Skip to content

Commit 7b2064b

Browse files
authored
gh-93353: Add test.support.late_deletion() (#93774)
1 parent df22eec commit 7b2064b

File tree

2 files changed

+41
-10
lines changed

2 files changed

+41
-10
lines changed

Lib/test/support/__init__.py

+37
Original file line numberDiff line numberDiff line change
@@ -2213,3 +2213,40 @@ def requires_venv_with_pip():
22132213
# True if Python is built with the Py_DEBUG macro defined: if
22142214
# Python is built in debug mode (./configure --with-pydebug).
22152215
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
2216+
2217+
2218+
def late_deletion(obj):
2219+
"""
2220+
Keep a Python alive as long as possible.
2221+
2222+
Create a reference cycle and store the cycle in an object deleted late in
2223+
Python finalization. Try to keep the object alive until the very last
2224+
garbage collection.
2225+
2226+
The function keeps a strong reference by design. It should be called in a
2227+
subprocess to not mark a test as "leaking a reference".
2228+
"""
2229+
2230+
# Late CPython finalization:
2231+
# - finalize_interp_clear()
2232+
# - _PyInterpreterState_Clear(): Clear PyInterpreterState members
2233+
# (ex: codec_search_path, before_forkers)
2234+
# - clear os.register_at_fork() callbacks
2235+
# - clear codecs.register() callbacks
2236+
2237+
ref_cycle = [obj]
2238+
ref_cycle.append(ref_cycle)
2239+
2240+
# Store a reference in PyInterpreterState.codec_search_path
2241+
import codecs
2242+
def search_func(encoding):
2243+
return None
2244+
search_func.reference = ref_cycle
2245+
codecs.register(search_func)
2246+
2247+
if hasattr(os, 'register_at_fork'):
2248+
# Store a reference in PyInterpreterState.before_forkers
2249+
def atfork_func():
2250+
pass
2251+
atfork_func.reference = ref_cycle
2252+
os.register_at_fork(before=atfork_func)

Lib/test/test_gc.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -1440,19 +1440,13 @@ def test_ast_fini(self):
14401440
code = textwrap.dedent("""
14411441
import ast
14421442
import codecs
1443+
from test import support
14431444
14441445
# Small AST tree to keep their AST types alive
14451446
tree = ast.parse("def f(x, y): return 2*x-y")
1446-
x = [tree]
1447-
x.append(x)
1448-
1449-
# Put the cycle somewhere to survive until the last GC collection.
1450-
# Codec search functions are only cleared at the end of
1451-
# interpreter_clear().
1452-
def search_func(encoding):
1453-
return None
1454-
search_func.a = x
1455-
codecs.register(search_func)
1447+
1448+
# Store the tree somewhere to survive until the last GC collection
1449+
support.late_deletion(tree)
14561450
""")
14571451
assert_python_ok("-c", code)
14581452

0 commit comments

Comments
 (0)