]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Make sure the pool can be deleted with no warning
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 13 Feb 2021 23:45:50 +0000 (00:45 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 12 Mar 2021 04:07:25 +0000 (05:07 +0100)
Make sure to delete reference loops between the pool and the maintenance
tasks after they have run.

Do not raise a warning if a connection in a pool is deleted without
being closed as this is a normal condition (imagining a pool being
created as a global object).

psycopg3/psycopg3/connection.py
psycopg3/psycopg3/pool.py
tests/test_pool.py

index 8fef6f82b685743afc7e6de8068776204931ec38..d6af27d4306be4f4831264e16ee7f28e9bb54fa1 100644 (file)
@@ -125,18 +125,23 @@ class BaseConnection(AdaptContext):
         pgconn.notice_handler = partial(BaseConnection._notice_handler, wself)
         pgconn.notify_handler = partial(BaseConnection._notify_handler, wself)
 
-        self._pool: Optional["ConnectionPool"] = None
+        # Attribute is only set if the connection is from a pool so we can tell
+        # apart a connection in the pool too (when _pool = None)
+        self._pool: Optional["ConnectionPool"]
 
     def __del__(self) -> None:
         # If fails on connection we might not have this attribute yet
         if not hasattr(self, "pgconn"):
             return
 
-        status = self.pgconn.transaction_status
-        if status == TransactionStatus.UNKNOWN:
+        # Connection correctly closed
+        if self.closed:
+            return
+
+        # Connection in a pool so terminating with the program is normal
+        if hasattr(self, "_pool"):
             return
 
-        status = TransactionStatus(status)
         warnings.warn(
             f"connection {self} was deleted while still open."
             f" Please use 'with' or '.close()' to close the connection",
@@ -466,7 +471,7 @@ class Connection(BaseConnection):
             self.commit()
 
         # Close the connection only if it doesn't belong to a pool.
-        if not self._pool:
+        if not getattr(self, "_pool", None):
             self.close()
 
     def close(self) -> None:
index b4a9eaf17986c79c718dbba4a3bf6df2a48e7948..769b008ddba6342c80352b6e9f473e460c87fd9f 100644 (file)
@@ -121,9 +121,10 @@ class ConnectionPool:
         return conn
 
     def putconn(self, conn: Connection) -> None:
-        if conn._pool is not self:
-            if conn._pool:
-                msg = f"it comes from pool {conn._pool.name!r}"
+        pool = getattr(conn, "_pool", None)
+        if pool is not self:
+            if pool:
+                msg = f"it comes from pool {pool.name!r}"
             else:
                 msg = "it doesn't come from any pool"
             raise ValueError(
@@ -136,6 +137,7 @@ class ConnectionPool:
     def _return_connection(self, conn: Connection) -> None:
         # Remove the pool reference from the connection before returning it
         # to the state, to avoid to create a reference loop.
+        # Also disable the warning for open connection in conn.__del__
         conn._pool = None
 
         self._reset_transaction_status(conn)
@@ -219,6 +221,10 @@ class ConnectionPool:
             if isinstance(task, StopWorker):
                 return
 
+            # delete reference loops which may keep the pool alive
+            del task.pool
+            del task
+
     def _connect(self) -> Connection:
         """Return a connection configured for the pool."""
         conn = Connection.connect(self.conninfo, **self.kwargs)
@@ -260,7 +266,9 @@ class MaintenanceTask:
         logger.debug("task running: %s", self)
 
     def __repr__(self) -> str:
-        return f"{self.__class__.__name__}({self.pool.name})"
+        return (
+            f"<{self.__class__.__name__} {self.pool.name!r} at 0x{id(self):x}>"
+        )
 
 
 class StopWorker(MaintenanceTask):
@@ -293,7 +301,7 @@ class AddConnection(MaintenanceTask):
         super().__call__()
 
         conn = self.pool._connect()
-        conn._pool = self.pool  # make it acceptable
+        conn._pool = self.pool  # make it accepted by the pool
         self.pool.putconn(conn)
 
 
index 66772a87172e7ad476a1bbb67ca0df28fd19c7b9..c4ec9e388487c537fee3c18d377098d1caad0636 100644 (file)
@@ -1,5 +1,6 @@
 import logging
-from time import time
+import weakref
+from time import time, sleep
 from threading import Thread
 
 import pytest
@@ -266,3 +267,17 @@ def test_putconn_wrong_pool(dsn):
     conn = p1.getconn()
     with pytest.raises(ValueError):
         p2.putconn(conn)
+
+
+def test_del_no_warning(dsn, recwarn):
+    p = pool.ConnectionPool(minconn=2)
+    with p.connection() as conn:
+        conn.execute("select 1")
+
+    while len(p._pool) < p.minconn:
+        sleep(0.01)
+
+    ref = weakref.ref(p)
+    del p
+    assert not ref()
+    assert not recwarn