Skip to content

fix(database): PostgreSQL recovers after a failed reconnect (#242) - #243

Merged
tina4stack merged 1 commit into
v3from
fix/pg-reconnect-recovery-242
Oct 19, 2026
Merged

tina4stack merged 1 commit into
v3from
fix/pg-reconnect-recovery-242

Conversation

@tina4stack

Copy link
Copy Markdown
Owner

Fixes #242 — PostgreSQL never recovered after a failed reconnect

Root cause (reproduced against real PG, not the reporter's code-read)

#236 recovers a link that dies during a statement, but execute/fetch/fetch_one
build the cursor first. On an already-closed link — the state _reconnect leaves when it
closed the old link and the replacement connect() failed (a failover before the new
primary answers) — self._conn.cursor() raises InterfaceError: connection already closed
before the reconnect path can run, so every later statement wedged there until restart.

Fix

_ensure_connection() rebuilds a dead, non-transactional link before a cursor is opened,
called at the top of execute/fetch/fetch_one. A reconnect that fails again propagates
but leaves the link dead, so the next statement retries rather than giving up.

Verification — NO MOCKS

Real PostgreSQL: a real dropped backend, a real failed reconnect (connect args aimed at a
dead port = the new primary not up yet), then real recovery once the good target returns.
Mutation-proven: the new test fails on the old code with exactly InterfaceError: connection already closed, passes with the fix; the five existing #236 cases stay green.

Parity

Reconnect-after-drop is a Python-only feature right now, so this bug exists only in
Python. PHP/Ruby/Node never re-establish a dropped PostgreSQL link at all — a separate,
larger parity gap (porting #236), flagged for its own task, not folded in here.

🤖 Generated with Claude Code

#236 reconnects a link the server drops DURING a statement, but
execute()/fetch()/fetch_one() build the cursor first. On a link that is
already closed - the state _reconnect leaves behind when it closed the
old link and the replacement connect() failed (a failover before the new
primary answers) - self._conn.cursor() raised "connection already closed"
before the reconnect path could run, and every later statement wedged
there until the process restarted.

Fix: _ensure_connection() rebuilds a dead, non-transactional link BEFORE a
cursor is opened on it, called at the top of execute/fetch/fetch_one. A
reconnect that fails again propagates but leaves the link dead, so the
next statement retries instead of giving up for good.

Verified against a REAL PostgreSQL (no mocks): a real dropped backend, a
real failed reconnect (connect args aimed at a dead port), then real
recovery when the good target returns. Mutation-proven - the new test
fails on the old code with exactly "InterfaceError: connection already
closed", passes with the fix; the five #236 cases stay green.

Parity note: PostgreSQL reconnect-after-drop is a Python-only feature
today, so this bug exists only in Python. PHP/Ruby/Node never
re-establish a dropped PostgreSQL link at all - that is a separate parity
gap (porting #236), not this fix.

Signed-off-by: Andre van Zuydam <andrevanzuydam@gmail.com>
Co-Authored-By: Tina4 <82961293+tina4stack@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andre van Zuydam <andrevanzuydam@gmail.com>
@tina4stack
tina4stack merged commit b8af2a7 into v3 Oct 19, 2026
14 checks passed
@tina4stack
tina4stack deleted the fix/pg-reconnect-recovery-242 branch October 19, 2026 21:13
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.

PostgreSQL reconnect: if the reconnect itself fails, the adapter never recovers (follow-up to #236)

2 participants