From 0355bea8c6fa5ba392ec8a1c6cce51f2e3a8d32e Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Mon, 13 Jun 2022 16:52:03 +0200 Subject: [PATCH 1/3] gh-93353: Add test.support.late_deletion() --- Lib/test/support/__init__.py | 35 +++++++++++++++++++++++++++++++++++ Lib/test/test_gc.py | 14 ++++---------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 780d04b020da49..64be60a51c2184 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2213,3 +2213,38 @@ 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 os.register_at_fork() callbacks + # - clear codecs.register() callbacks + + ref_cycle = [obj] + ref_cycle.append(ref_cycle) + + # PyInterpreterState.codec_search_path + import codecs + def search_func(encoding): + return None + search_func.reference = ref_cycle + codecs.register(search_func) + + # 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 dbbd67b4fc88a1..087f72768fa4bf 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) From 84eb078604fecda942a74482f83960bcb9c45f78 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Mon, 13 Jun 2022 20:05:47 +0200 Subject: [PATCH 2/3] Rephrase the documentation --- Lib/test/support/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 64be60a51c2184..e0fdffe9d3bc98 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2229,21 +2229,22 @@ def late_deletion(obj): # Late CPython finalization: # - finalize_interp_clear() - # - _PyInterpreterState_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) - # PyInterpreterState.codec_search_path + # 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) - # PyInterpreterState.before_forkers + # Store a reference in PyInterpreterState.before_forkers def atfork_func(): pass atfork_func.reference = ref_cycle From 148bcff880a11c3713778f11021cf0429c6ca40d Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Mon, 13 Jun 2022 21:54:49 +0200 Subject: [PATCH 3/3] Fix Windows support --- Lib/test/support/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index e0fdffe9d3bc98..4baaeb766317f1 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2244,8 +2244,9 @@ def search_func(encoding): search_func.reference = ref_cycle codecs.register(search_func) - # Store a reference in PyInterpreterState.before_forkers - def atfork_func(): - pass - atfork_func.reference = ref_cycle - os.register_at_fork(before=atfork_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)