Skip to content

fix: retry a connection SQLAlchemy invalidated - #69

Merged
lesnik512 merged 1 commit into
mainfrom
fix/retry-closed-connection-68
Oct 16, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
fix/retry-closed-connection-68

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #68.

postgres_retry now also retries a DBAPIError that SQLAlchemy marked connection_invalidated. That covers asyncpg's InterfaceError: connection is closed, which a pooled connection that died while idle raises on its next use. Before, that error failed the request, so pool_pre_ping=True was the only protection after a Postgres restart or failover.

The check uses SQLAlchemy's flag instead of adding asyncpg.InterfaceError to the tuple. Most InterfaceError raise sites are deterministic misuse ("another operation is in progress", wrong argument counts, cursor errors), and retrying those only delays the failure. The asyncpg dialect sets the flag from is_closed() on the driver connection. Raw asyncpg errors stay class-only: without the flag, the closed case can only be recognised by its message.

The shared-session half of #68 is a documentation change. A retry cannot recover a session that is still inside the failed transaction, and postgres_retry cannot see the session to roll it back. The README now says the retried coroutine must open its own Transaction, and the first example does that.

Changes:

  • db_retry/retriable.py: _is_retriable_link accepts connection_invalidated before the asyncpg isinstance.
  • docs/adr/0002: amendment recording the flag as the second axis that ADR left room for.
  • CONTEXT.md: "Retriable error" now includes the flag.
  • README.md: the retry precondition, the first example rewritten to use Transaction, and the API reference.

Tests:

  • test_retriable.py: a connection_invalidated error is retriable, and an InterfaceError on a live connection is not.
  • test_retry.py: terminates the backend of the one pooled connection, waits for asyncpg to see it close, then checks that a retried Transaction succeeds on the second attempt. It fails without the change in retriable.py.

Downstream: chat-app has six use cases that retry on an injected session without Transaction (authenticate_user, check_token_revoked, fetch_chat, fetch_chats, fetch_messages, fetch_user). They need a follow-up once this is released.

Retry any DBAPIError marked connection_invalidated, which covers asyncpg's InterfaceError on a pooled connection that closed while idle. Document that the retried coroutine must open its own Transaction.
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.

postgres_retry fails on a shared session and misses the closed-connection InterfaceError

1 participant