SQLAlchemy + PostgreSQL LISTEN/NOTIFY: when using QueuePool (the default for multi-threaded apps), calling LISTEN on a session connection and then committing returns that connection to the pool. Subsequent poll() calls may check out a different connection that never executed LISTEN, so pg_notify notifications are never delivered. The symptom is silent degradation — the application falls back to polling at POLL_TIMEOUT intervals instead of waking on NOTIFY. No error is raised. This was discovered in beanqueue (bq), but affects any SQLAlchemy app that uses LISTEN/NOTIFY with QueuePool. SingletonThreadPool masks the issue because it pins one connection per thread. Tried: calling LISTEN before commit, expecting the LISTEN state to persist across checkouts. Also tried relying on the session's connection() to always return the same connection — it doesn't with QueuePool.
PostgreSQL LISTEN is connection-scoped — it's lost when the connection is returned to the pool and a different one is checked out. The fix is to open a dedicated connection outside the pool for LISTEN/NOTIFY:
# Open a dedicated AUTOCOMMIT connection for LISTEN
notify_conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT")
# Execute LISTEN on this dedicated connection
notify_conn.exec_driver_sql('LISTEN "my_channel"')
# Poll on the same connection
driver = notify_conn.connection.driver_connection
driver.poll()
if driver.notifies:
# process notifications
driver.notifies.clear()Key points:
isolation_level="AUTOCOMMIT" — LISTEN doesn't work inside a transaction in some configurations, and AUTOCOMMIT ensures NOTIFYs are delivered immediately.OperationalError on poll, close the old connection, and reopen via the same pattern.This applies to any SQLAlchemy 2.0 app using LISTEN/NOTIFY with QueuePool. SingletonThreadPool accidentally masks the bug because it returns the same connection per thread.