[Python-checkins] gh-93353: Add test.support.late_deletion() (#93774)

vstinner webhook-mailer at python.org
Mon Jun 13 19:09:33 EDT 2022


https://github.com/python/cpython/commit/7b2064b4b942e1d3c7fd74b5c463250319bc20fb
commit: 7b2064b4b942e1d3c7fd74b5c463250319bc20fb
branch: main
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2022-06-14T01:09:23+02:00
summary:

gh-93353: Add test.support.late_deletion() (#93774)

files:
M Lib/test/support/__init__.py
M Lib/test/test_gc.py

diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 780d04b020da4..4baaeb766317f 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -2213,3 +2213,40 @@ def requires_venv_with_pip():
 # True if Python is built with the Py_DEBUG macro defined: if
 # Python is built in debug mode (./configure --with-pydebug).
 Py_DEBUG = hasattr(sys, 'gettotalrefcount')
+
+
+def late_deletion(obj):
+    """
+    Keep a Python alive as long as possible.
+
+    Create a reference cycle and store the cycle in an object deleted late in
+    Python finalization. Try to keep the object alive until the very last
+    garbage collection.
+
+    The function keeps a strong reference by design. It should be called in a
+    subprocess to not mark a test as "leaking a reference".
+    """
+
+    # Late CPython finalization:
+    # - finalize_interp_clear()
+    # - _PyInterpreterState_Clear(): Clear PyInterpreterState members
+    #   (ex: codec_search_path, before_forkers)
+    # - clear os.register_at_fork() callbacks
+    # - clear codecs.register() callbacks
+
+    ref_cycle = [obj]
+    ref_cycle.append(ref_cycle)
+
+    # Store a reference in PyInterpreterState.codec_search_path
+    import codecs
+    def search_func(encoding):
+        return None
+    search_func.reference = ref_cycle
+    codecs.register(search_func)
+
+    if hasattr(os, 'register_at_fork'):
+        # Store a reference in PyInterpreterState.before_forkers
+        def atfork_func():
+            pass
+        atfork_func.reference = ref_cycle
+        os.register_at_fork(before=atfork_func)
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index dbbd67b4fc88a..087f72768fa4b 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1440,19 +1440,13 @@ def test_ast_fini(self):
         code = textwrap.dedent("""
             import ast
             import codecs
+            from test import support
 
             # Small AST tree to keep their AST types alive
             tree = ast.parse("def f(x, y): return 2*x-y")
-            x = [tree]
-            x.append(x)
-
-            # Put the cycle somewhere to survive until the last GC collection.
-            # Codec search functions are only cleared at the end of
-            # interpreter_clear().
-            def search_func(encoding):
-                return None
-            search_func.a = x
-            codecs.register(search_func)
+
+            # Store the tree somewhere to survive until the last GC collection
+            support.late_deletion(tree)
         """)
         assert_python_ok("-c", code)
 



More information about the Python-checkins mailing list