From 245facd17f7d42d85c20c6dc432e6a8963ef4359 Mon Sep 17 00:00:00 2001 From: pchaseh Date: Tue, 19 May 2026 09:46:07 -0300 Subject: [PATCH] Fix connection pool deadlock Closes #1307 --- asyncpg/pool.py | 9 +++++++-- tests/test_pool.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/asyncpg/pool.py b/asyncpg/pool.py index 5c7ea9ca..3a90c35f 100644 --- a/asyncpg/pool.py +++ b/asyncpg/pool.py @@ -199,8 +199,13 @@ async def release(self, timeout: Optional[float]) -> None: 'a free connection holder') if self._con.is_closed(): - # When closing, pool connections perform the necessary - # cleanup, so we don't have to do anything else here. + # The connection was closed/aborted, possibly without going + # through Connection.close() or Connection.terminate() (e.g. + # when the protocol calls abort() on a fatal server error). + # In that case, _release_on_close() may never have been called, + # so we call _release() here to ensure the holder is returned + # to the pool queue. + self._release() return self._timeout = None diff --git a/tests/test_pool.py b/tests/test_pool.py index 695363b7..07cc4fb6 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -1004,6 +1004,19 @@ async def worker(): conn = await pool.acquire(timeout=0.1) await pool.release(conn) + async def test_pool_release_after_protocol_abort(self): + pool = await self.create_pool(min_size=1, max_size=1) + + conn = await pool.acquire() + conn._con._protocol.abort() + + await pool.release(conn) + + # Check the connection has been released back to the pool + conn2 = await pool.acquire(timeout=1.0) + await pool.release(conn2) + await pool.close() + @unittest.skipIf(os.environ.get('PGHOST'), 'unmanaged cluster') class TestPoolReconnectWithTargetSessionAttrs(tb.ClusterTestCase):