Skip to content

PostgreSQL: force wire-level BEGIN when starting a transaction - #1221

Merged
headius merged 1 commit into
jruby:72-stablefrom
ryudoawaru:pg-force-wire-begin
Jul 24, 2026
Merged

PostgreSQL: force wire-level BEGIN when starting a transaction#1221
headius merged 1 commit into
jruby:72-stablefrom
ryudoawaru:pg-force-wire-begin

Conversation

@ryudoawaru

Copy link
Copy Markdown

Problem

On PostgreSQL, begin_db_transaction ends up calling Connection#setAutoCommit(false) — but in pgjdbc that only flips a client-side flag. The actual BEGIN is deferred and piggybacked onto the next statement sent (see pgjdbc's QueryExecutorImpl#sendQueryPreamble).

Rails 7.1+ transactions are lazy on their side too: materialize_transactions calls begin_db_transaction and expects a transaction to actually be open on the server afterwards. Under AR-JDBC that materialization was a wire-level no-op — the server session stays IDLE until the next query, so anything that inspects the raw transaction state right after materialization (e.g. Rails' own adapter_test.rb via raw_transaction_open?) sees no transaction.

Fix

After setAutoCommit(false) (and setting isolation, when given), issue a no-op SELECT 1. pgjdbc prepends its deferred BEGIN to it in the same round-trip, so the cost matches the pg gem's exec("BEGIN") — one round-trip, just moved from the first in-transaction query to begin_db_transaction where AR expects it.

Includes a test that asserts the server-side transaction state is OPEN right after begin_db_transaction (it reports IDLE without the fix).

Verification

Environment: JRuby 9.4.14.0, PostgreSQL 18.4, pgjdbc 42.7.11, macOS.

  • rake test_postgresql: 306 tests, 0 failures, 0 errors (includes the new test; fails without the patch)
  • Rails suite (rails:test_postgresql, AR 7-2-stable), on top of 72-stable + Expose PG-compatible status/transaction_status/async_exec on the JDBC connection (+ CI Postgres 14) #1220:
    • test/cases/adapter_test.rb: 69 runs, 0 failures, 0 errors — the 4 remaining failures there (assert raw_transaction_open? right after materialize_transactions, lines 533/543/553/577) are exactly what this patch fixes
    • same with PREPARED_STATEMENTS=false: 67 runs, 0 failures, 0 errors
    • test/cases/transactions_test.rb: 105 runs, 0 failures, 0 errors

Related: #1173, #1218, #1220.

🤖 Generated with Claude Code

pgjdbc's setAutoCommit(false) only flips a client-side flag - the actual
BEGIN is deferred and piggybacked onto the next statement sent
(QueryExecutorImpl#sendQueryPreamble). Rails 7.1+ lazy transactions assume
begin_db_transaction opens a server-side transaction once materialized,
so materialize_transactions was a wire-level no-op under AR-JDBC and the
server stayed IDLE.

Flush the deferred BEGIN by issuing a no-op SELECT 1 right after
setAutoCommit(false); pgjdbc prepends its BEGIN to it in the same
round-trip, matching the cost of the pg gem's exec("BEGIN").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@headius headius left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks simple enough and good to note that it has no more overhead than the equivalent exec("BEGIN") (I immediately had a concern about the additional round-trip).

Approved by me but would be good to get input from the other active PG contributors @skunkworker @JesseChavez.

@ryudoawaru

Copy link
Copy Markdown
Author

A note on the failing Rails-suite checks — they are inherited from 72-stable, not introduced here.

The Rails Tests (Postgres) jobs have been red on 72-stable for every recent push, well before this PR:

On this PR, the PS=true job actually completes the full suite for the first time in a while — 9481 runs, 43 failures, 74 errors (log). Those break down as:

The PS=false job died ~2 minutes in from connection-pool exhaustion, the same long-standing issue as the too many clients crashes above.

The ARJDBC Tests (Postgres) jobs (all 4 combinations) are green, including the new server-side-transaction-state test from this PR.

@headius
headius merged commit 87198d3 into jruby:72-stable Jul 24, 2026
7 of 12 checks passed
@headius

headius commented Jul 24, 2026

Copy link
Copy Markdown
Member

Thank you @ryudoawaru! I will talk with other contributors and see about getting some releases out. Do you have other fixes coming?

@ryudoawaru

Copy link
Copy Markdown
Author

Thanks for merging! Two things I'm looking at next: forward-porting this fix to master, and #1212 — I've tracked down the root cause with a reproduction and posted the analysis there. Let me know if something else has priority for the releases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants