]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix(pool): reset connection transaction status after failed check
authorKanav Kalucha <kanav@openai.com>
Tue, 25 Feb 2025 18:28:38 +0000 (10:28 -0800)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 18 Oct 2025 23:57:46 +0000 (01:57 +0200)
Close #1014

psycopg_pool/psycopg_pool/pool.py
psycopg_pool/psycopg_pool/pool_async.py
tests/pool/test_pool.py
tests/pool/test_pool_async.py

index 64906e60dd904ababef55bdf58ca9b220fd9d5b9..63cd806e0b554329b89ceeab36748ebb46867e39 100644 (file)
@@ -669,6 +669,7 @@ class ConnectionPool(Generic[CT], BasePool):
         """
         Return a connection to the pool after usage.
         """
+        self._reset_connection(conn)
         if from_getconn:
             if conn.pgconn.transaction_status == TransactionStatus.UNKNOWN:
                 self._stats[self._CONNECTIONS_LOST] += 1
@@ -676,14 +677,12 @@ class ConnectionPool(Generic[CT], BasePool):
                 self.run_task(AddConnection(self))
                 logger.info("not serving connection found broken")
                 return
-        else:
-            self._reset_connection(conn)
-            if conn.pgconn.transaction_status == TransactionStatus.UNKNOWN:
-                self._stats[self._RETURNS_BAD] += 1
-                # Connection no more in working state: create a new one.
-                self.run_task(AddConnection(self))
-                logger.warning("discarding closed connection: %s", conn)
-                return
+        elif conn.pgconn.transaction_status == TransactionStatus.UNKNOWN:
+            self._stats[self._RETURNS_BAD] += 1
+            # Connection no more in working state: create a new one.
+            self.run_task(AddConnection(self))
+            logger.warning("discarding closed connection: %s", conn)
+            return
 
         # Check if the connection is past its best before date
         if conn._expire_at <= monotonic():
index f39605e918547e744c09214de4530d7180519a3e..8c511be49c17203f5e15bd777d748431f786de24 100644 (file)
@@ -724,6 +724,7 @@ class AsyncConnectionPool(Generic[ACT], BasePool):
         """
         Return a connection to the pool after usage.
         """
+        await self._reset_connection(conn)
         if from_getconn:
             if conn.pgconn.transaction_status == TransactionStatus.UNKNOWN:
                 self._stats[self._CONNECTIONS_LOST] += 1
@@ -733,7 +734,6 @@ class AsyncConnectionPool(Generic[ACT], BasePool):
                 return
 
         else:
-            await self._reset_connection(conn)
             if conn.pgconn.transaction_status == TransactionStatus.UNKNOWN:
                 self._stats[self._RETURNS_BAD] += 1
                 # Connection no more in working state: create a new one.
index a0e6b2aec4af7f9b51d09968f7450669f5c8cc47..1b7415619052363091e4ae7342c6732048bb61e5 100644 (file)
@@ -1017,6 +1017,28 @@ def test_check_backoff(dsn, caplog, monkeypatch):
         want *= 2
 
 
+@pytest.mark.slow
+@pytest.mark.parametrize("status", ["ERROR", "INTRANS"])
+def test_check_returns_an_ok_connection(dsn, status):
+
+    def check(conn):
+        if status == "ERROR":
+            conn.execute("wat")
+        elif status == "INTRANS":
+            conn.execute("select 1")
+            1 / 0
+        else:
+            assert False
+
+    with pool.ConnectionPool(dsn, min_size=1, check=check) as p:
+        p.wait(1.0)
+        with pytest.raises(pool.PoolTimeout):
+            conn = p.getconn(0.5)
+
+        conn = list(p._pool)[0]
+        assert conn.info.transaction_status == TransactionStatus.IDLE
+
+
 def test_override_close(dsn):
     # Verify that it's possible to override `close()` to act as `putconn()`.
     # which allows to use the psycopg pool in a sqlalchemy NullPool.
index a301b07e05d7d26ea14295d82891a3e751c9d242..ff8509b7d1a137ef7e75b50517724fe356dc58ad 100644 (file)
@@ -1020,6 +1020,27 @@ async def test_check_backoff(dsn, caplog, monkeypatch):
         want *= 2
 
 
+@pytest.mark.slow
+@pytest.mark.parametrize("status", ["ERROR", "INTRANS"])
+async def test_check_returns_an_ok_connection(dsn, status):
+    async def check(conn):
+        if status == "ERROR":
+            await conn.execute("wat")
+        elif status == "INTRANS":
+            await conn.execute("select 1")
+            1 / 0
+        else:
+            assert False
+
+    async with pool.AsyncConnectionPool(dsn, min_size=1, check=check) as p:
+        await p.wait(1.0)
+        with pytest.raises(pool.PoolTimeout):
+            conn = await p.getconn(0.5)
+
+        conn = list(p._pool)[0]
+        assert conn.info.transaction_status == TransactionStatus.IDLE
+
+
 async def test_override_close(dsn):
     # Verify that it's possible to override `close()` to act as `putconn()`.
     # which allows to use the psycopg pool in a sqlalchemy NullPool.