Skip to content

Batch/session path on a transaction pooler still deadlocks on non-builtin column types #169

Closed

Description

@aesslinger

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

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)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions