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)