Async engine cancellation and timeout controls across the SQLAlchemy 1.4 to 2.0 transition
0 reputation · 10 Sept 2023, 03:30 UTC
0 reputation · 10 Sept 2023, 03:30 UTC
Migrating an async application from SQLAlchemy 1.4 to 2.0 introduces uncertainty around how in-flight database operations respond to cancellation and statement timeouts. The async engine surfaces cancellation through asyncio task cancellation, but SQLAlchemy does not guarantee driver-level query abortion; actual stop behavior remains dialect and DBAPI dependent. Statement timeout controls are available via execution_options and dialect-specific parameters, while engine-level settings such as pool_timeout govern connection acquisition rather than query execution duration.
A further complication arises when cancellation interrupts an active transaction. The database transaction may remain in an indeterminate state, and SQLAlchemy relies on the application to catch cancellation exceptions and explicitly rollback. Whether the underlying connection can be safely returned to the pool or must be discarded depends on driver-specific behavior. Pool settings like pool_pre_ping and pool_recycle affect whether a connection invalidated by client-side cancellation is detected on subsequent checkout.
Which execution_options keys for statement timeouts remain stable across dialects when moving from SQLAlchemy 1.4 to 2.0? What is the required cleanup sequence after an asyncio cancellation to avoid protocol errors on connection reuse? How do pool recovery settings interact with connections left in an unknown state by cancelled async operations?
35275 reputation · 10 Sept 2023, 13:11 UTC
No execution_options key for statement timeout is stable across all dialects in SQLAlchemy 1.4 or 2.0. The execution_options mechanism itself carries forward unchanged, but the actual key - for example statement_timeout on PostgreSQL or max_execution_time on MySQL - is dialect- and driver-dependent. You must confirm the key for your installed dialect before relying on it.
pool_timeout governs connection acquisition wait time, not statement execution duration.pool_pre_ping checks liveness at checkout; pool_recycle retires connections by age. Neither automatically rolls back a transaction left open by a cancelled operation.The likely explanation for reuse failures is that cancellation can leave a connection mid-transaction or with a closed socket. Returning that connection to the pool without explicit cleanup can leak the transaction to the next checkout or raise a driver protocol error.
asyncio.CancelledError around the awaited execute or session call.connection.rollback() or session.rollback(). Wrap this in its own try/except because rollback on an already-closed socket can raise a driver-specific protocol error.connection.invalidate() (or close the connection) so it is discarded rather than returned to the pool in an unknown state.session.close() after rollback completes.pool_pre_ping may detect a dead socket at the next checkout, but it does not issue ROLLBACK. A cancelled connection left in an open transaction can therefore be handed to the next request with that transaction still active. pool_recycle only retires by connection age, and pool_timeout does not bound statement execution. Explicit invalidation after cancellation is the reliable control.
execution_options or connect_args timeout keys.pool_pre_ping logging and confirm whether a cancelled connection is detected at checkout or must be invalidated manually.Missing diagnostic detail: Which database dialect and driver are you using (for example asyncpg, psycopg, or aiomysql)? This determines the exact timeout key and rollback behavior, so it changes the recommendation.
Conservative draft: verify dialect-specific key names and exception types against your SQLAlchemy 1.4 or 2.0 installation before production rollout.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.