Skip to content

fix(node-postgres): release pool client when BEGIN rejects in transaction()#6032

Open
allocsys wants to merge 1 commit into
drizzle-team:mainfrom
allocsys:fix/6023-node-postgres-begin-release-leak
Open

fix(node-postgres): release pool client when BEGIN rejects in transaction()#6032
allocsys wants to merge 1 commit into
drizzle-team:mainfrom
allocsys:fix/6023-node-postgres-begin-release-leak

Conversation

@allocsys

Copy link
Copy Markdown

Fixes #6023

Problem

NodePgSession.transaction() checks a client out of a pg.Pool, then runs BEGIN before entering the try/finally that calls release(). If BEGIN rejects (e.g. the connection drops right after checkout), control skips both the catch (which would ROLLBACK) and the finally (which releases the client) — the checked-out client is never returned to the pool. Repeated failures can exhaust it.

Fix

Move BEGIN inside the existing try/finally and track whether it actually succeeded via a began flag, so ROLLBACK is only attempted once a transaction has genuinely started, while release() still runs on every exit path:

let began = false;
try {
  await tx.execute(sql`begin${config ? sql` ${tx.getTransactionConfigSQL(config)}` : undefined}`);
  began = true;
  const result = await transaction(tx);
  await tx.execute(sql`commit`);
  return result;
} catch (error) {
  if (began) await tx.execute(sql`rollback`);
  throw error;
} finally {
  if (isPool) (session.client as PoolClient).release();
}

Verification

Ran the minimal repro from the issue (a FailingPool/client stub whose query always rejects, simulating a dropped connection on BEGIN) directly against the src in both states:

  • Unpatched main: released: 0 — leak reproduces
  • This branch: released: 1 — client released, no leak

Note: the currently-published drizzle-orm@0.45.2 (npm latest) already contains an equivalent guard, so current stable users aren't affected — this brings main in line with that existing fix. Left more detail on this in the issue thread.

Scope note

Nested transactions (NodePgTransaction.transaction(), the savepoint path) have a similar "no guard around the initial statement" shape, but there's no pool client to leak there since it reuses the existing session — left out of this PR to keep it minimal/single-purpose. Happy to follow up separately if maintainers want that covered too.

…tion()

BEGIN previously ran before the try/finally, so a rejected BEGIN
(e.g. connection dropped right after pool.connect()) skipped both
the catch (ROLLBACK) and the finally (release()), leaking the
checked-out pool client. Repeated failures could exhaust the pool.

Moves BEGIN inside the try/finally and tracks whether it succeeded
via a `began` flag, only attempting ROLLBACK if a transaction
actually started. Client is now released on every exit path once
pool.connect() has succeeded. Matches the existing fix already
present in the 0.x line's node-postgres session.ts.

Fixes drizzle-team#6023
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.

[BUG]: node-postgres pool client is not released when transaction BEGIN rejects

1 participant