]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
check fairy for None in pool cleanup
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 4 Oct 2023 13:42:55 +0000 (09:42 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 4 Oct 2023 13:42:55 +0000 (09:42 -0400)
Fixed issue where under some garbage collection / exception scenarios the
connection pool's cleanup routine would raise an error due to an unexpected
set of state, which can be reproduced under specific conditions.

Fixes: #10414
Change-Id: Ie732f23290d0d3d641f37cd2fee55aff5b9d0857

doc/build/changelog/unreleased_20/10414.rst [new file with mode: 0644]
lib/sqlalchemy/pool/base.py
test/engine/test_pool.py

diff --git a/doc/build/changelog/unreleased_20/10414.rst b/doc/build/changelog/unreleased_20/10414.rst
new file mode 100644 (file)
index 0000000..6c7159c
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, engine
+    :tickets: 10414
+
+    Fixed issue where under some garbage collection / exception scenarios the
+    connection pool's cleanup routine would raise an error due to an unexpected
+    set of state, which can be reproduced under specific conditions.
index 915dc400b97931ffbd44f0d1b6a5f6e67fa9a22e..90ed32ec27beedcbaf29554f1f3d6253975523cf 100644 (file)
@@ -1040,7 +1040,9 @@ def _finalize_fairy(
     # test/engine/test_pool.py::PoolEventsTest::test_checkin_event_gc[True]
     # which actually started failing when pytest warnings plugin was
     # turned on, due to util.warn() above
-    fairy.dbapi_connection = fairy._connection_record = None  # type: ignore
+    if fairy is not None:
+        fairy.dbapi_connection = None  # type: ignore
+        fairy._connection_record = None
     del dbapi_connection
     del connection_record
     del fairy
index e2e484174085cf24dfecbd4d57ae422aed641b6e..44c494bad4abb331bc1c72fdab69c5042f0bd9df 100644 (file)
@@ -738,7 +738,10 @@ class PoolEventsTest(PoolTestBase):
 
     @testing.variation("is_asyncio", [(True, testing.requires.asyncio), False])
     @testing.variation("has_terminate", [True, False])
-    def test_checkin_event_gc(self, is_asyncio, has_terminate):
+    @testing.variation("invalidate_conn_rec", [True, False])
+    def test_checkin_event_gc(
+        self, is_asyncio, has_terminate, invalidate_conn_rec
+    ):
         """tests for #8419, which have been modified for 2.0 in #9237"""
 
         p, canary = self._checkin_event_fixture(
@@ -751,7 +754,11 @@ class PoolEventsTest(PoolTestBase):
 
         eq_(canary, [])
 
-        if is_asyncio:
+        if invalidate_conn_rec:
+            # test #10414
+            c1._connection_record.invalidate()
+
+        if is_asyncio and not invalidate_conn_rec:
             if has_terminate:
                 with expect_warnings(
                     "The garbage collector is trying to clean up.*which will "
@@ -772,7 +779,9 @@ class PoolEventsTest(PoolTestBase):
 
         detach_gced = is_asyncio
 
-        if detach_gced:
+        if invalidate_conn_rec:
+            eq_(canary, ["checkin"])
+        elif detach_gced:
             if has_terminate:
                 eq_(canary, ["reset_no_rollback", "detach", "close_detached"])
             else:
@@ -784,7 +793,7 @@ class PoolEventsTest(PoolTestBase):
             eq_(canary, ["reset_rollback_ok", "checkin"])
 
         gc_collect()
-        if detach_gced:
+        if detach_gced or invalidate_conn_rec:
             is_none(dbapi_connection())
         else:
             is_not_none(dbapi_connection())
@@ -1708,8 +1717,11 @@ class QueuePoolTest(PoolTestBase):
         "detach_gced",
         [("detached_gc", testing.requires.asyncio), "normal_gc"],
     )
+    @testing.variation("invalidate_conn_rec", [True, False])
     @testing.emits_warning("The garbage collector")
-    def test_userspace_disconnectionerror_weakref_finalizer(self, detach_gced):
+    def test_userspace_disconnectionerror_weakref_finalizer(
+        self, detach_gced, invalidate_conn_rec
+    ):
         dbapi, pool = self._queuepool_dbapi_fixture(
             pool_size=1, max_overflow=2, _is_asyncio=detach_gced
         )
@@ -1740,6 +1752,10 @@ class QueuePoolTest(PoolTestBase):
         )
 
         not_closed_dbapi_conn = conn.dbapi_connection
+
+        if invalidate_conn_rec:
+            conn._connection_record.invalidate()
+
         del conn
 
         gc_collect()
@@ -1752,7 +1768,11 @@ class QueuePoolTest(PoolTestBase):
             # this creates a gc-level warning that is not easy to pin down,
             # hence we use the testing.emits_warning() decorator just to squash
             # it
-            eq_(not_closed_dbapi_conn.mock_calls, [call.rollback()])
+
+            if invalidate_conn_rec:
+                eq_(not_closed_dbapi_conn.mock_calls, [call.close()])
+            else:
+                eq_(not_closed_dbapi_conn.mock_calls, [call.rollback()])
 
     @testing.requires.timing_intensive
     def test_recycle_pool_no_race(self):