From 44034f19ac27ccd4a0e57dfa3d2d6b494dc9b133 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 14 Jan 2021 18:07:14 -0500 Subject: Create explicit GC ordering between ConnectionFairy/ConnectionRecord Fixed issue where connection pool would not return connections to the pool or otherwise be finalized upon garbage collection under pypy if the checked out connection fell out of scope without being closed. This is a long standing issue due to pypy's difference in GC behavior that does not call weakref finalizers if they are relative to another object that is also being garbage collected. A strong reference to the related record is now maintained so that the weakref has a strong-referenced "base" to trigger off of. Fixes: #5842 Change-Id: Id5448fdacb6cceaac1ea40b2fbc851f052ed8e86 --- lib/sqlalchemy/testing/assertions.py | 12 ++++-------- lib/sqlalchemy/testing/engines.py | 14 ++++++++++++++ lib/sqlalchemy/testing/plugin/plugin_base.py | 1 + 3 files changed, 19 insertions(+), 8 deletions(-) (limited to 'lib/sqlalchemy/testing') diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index 40549f54c..f2ed91b79 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -228,6 +228,10 @@ def is_none(a, msg=None): is_(a, None, msg=msg) +def is_not_none(a, msg=None): + is_not(a, None, msg=msg) + + def is_true(a, msg=None): is_(bool(a), True, msg=msg) @@ -236,14 +240,6 @@ def is_false(a, msg=None): is_(bool(a), False, msg=msg) -def is_none(a, msg=None): - is_(a, None, msg=msg) - - -def is_not_none(a, msg=None): - is_not(a, None, msg=msg) - - def is_(a, b, msg=None): """Assert a is b, with repr messaging on failure.""" assert a is b, msg or "%r is not %r" % (a, b) diff --git a/lib/sqlalchemy/testing/engines.py b/lib/sqlalchemy/testing/engines.py index 8b334fde2..a313c298a 100644 --- a/lib/sqlalchemy/testing/engines.py +++ b/lib/sqlalchemy/testing/engines.py @@ -14,6 +14,7 @@ import weakref from . import config from .util import decorator +from .util import gc_collect from .. import event from .. import pool @@ -124,6 +125,19 @@ class ConnectionKiller(object): self._drop_testing_engines("function") self._drop_testing_engines("class") + def stop_test_class_outside_fixtures(self): + # ensure no refs to checked out connections at all. + + if pool.base._strong_ref_connection_records: + gc_collect() + + if pool.base._strong_ref_connection_records: + ln = len(pool.base._strong_ref_connection_records) + pool.base._strong_ref_connection_records.clear() + assert ( + False + ), "%d connection recs not cleared after test suite" % (ln) + def final_cleanup(self): self.checkin_all() for scope in self.testing_engines: diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py index 7851fbb3e..858814f91 100644 --- a/lib/sqlalchemy/testing/plugin/plugin_base.py +++ b/lib/sqlalchemy/testing/plugin/plugin_base.py @@ -586,6 +586,7 @@ def stop_test_class(cls): def stop_test_class_outside_fixtures(cls): + engines.testing_reaper.stop_test_class_outside_fixtures() provision.stop_test_class_outside_fixtures(config, config.db, cls) try: if not options.low_connections: -- cgit v1.2.1