Repository navigation
Batch/session path on a transaction pooler still deadlocks on non-builtin column types #169
Description
Summary
The #166 fix (PR #168) and the #164 pinned one-shot path both only cover non-session single result-set queries. An interactive transaction session on a transaction-mode pooler (pooler_mode=transaction + session_id set) still deadlocks on a table with a non-builtin column type (custom enum, composite, extension type) once the result is large enough to fill the channel buffer — the same typeinfo_statement mid-stream interleave as #166.
Root cause
execute_query routes session_id.is_some() to run_batch_in_session (line 31) before the exec_query routing check (line 306) that sends pooler result-set queries to #164's exec_query_pooled_txn pinned path. run_batch_in_session calls exec_query_on_client with is_transaction_pooler=true, so the #168 pre-resolve prepare is gated off (!is_transaction_pooler is false), and query_typed runs without the type cache warm → get_type fires typeinfo_statement mid-stream over the same channel delivering DataRows → deadlock once the row data fills the bounded responses channel + socket buffer.
This is the same mechanism as #166 (direct connections) and #160/#163 (pooler single queries), just on the batch/session path, which neither #164 nor #168 covered.
Reproduction
A transaction-mode pooler (PgBouncer pool_mode=transaction, pooler_mode=transaction in Tabularis) + an interactive transaction session (the user runs multi-statement transactions in a tab, so session_id is set) + SELECT * FROM table_with_enum_column with enough rows to fill the buffer (the #166 repro's 600 rows × 500-byte pad at limit 50 triggers it).
Why it's not fixed by #164 or #168
- fix: pin backend for transaction-pooler result queries on non-builtin column types (#160, #163) #164's pinned one-shot path (
exec_query_pooled_txn) only handles non-session single result-set queries —run_batch_in_sessionbypasses the routing at line 306 entirely. - fix: pre-resolve column types on direct connections to stop enum-table hang (#166) #168's pre-resolve gates on
!is_transaction_poolerinexec_query_on_client, so a pooler connection (which passesis_transaction_pooler=true) skips theprepare.
Suggested fix direction
run_batch_in_session owns its own transaction and pins the connection to the session — it can't use the one-shot pool (that would break the session's transaction continuity). The fix is a DEALLOCATE ALL + prepare pre-resolve on the session-pinned connection before each result-set query, inside the session's existing transaction — analogous to what run_pinned_body does on the one-shot path, but on the shared session connection. This is a non-trivial change to the batch path's transaction handling and deserves its own design.
Scope
Pre-existing since rc.9 (#149). Not introduced by #168. Filed as a follow-up so the direct-connection fix (#168) can land without scope creep into the batch path.
\cc @ductrihn @albertorizzi (reporters of #166, where the pooler interaction was characterized)
Activity
fix: stop one-shot routing from evicting the batch cancel guard (#172)
fix: pre-resolve column types on batch/session pooler path (#169) (#172)
fix: address pre-handoff-review findings on #173 classifier and routing
fix: keep bare [SET/CREATE TEMP, SELECT] batch on the shared client o…
fix: route ping/connect/cancel to dedicated lanes so stuck queries do…
chore: release 1.0.0-rc.12 (#178)