]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151728: Clear the typing caches at interpreter shutdown (GH-155002)
authorWenzel Jakob <wenzel.jakob@epfl.ch>
Mon, 3 Aug 2026 15:21:20 +0000 (17:21 +0200)
committerGitHub <noreply@github.com>
Mon, 3 Aug 2026 15:21:20 +0000 (17:21 +0200)
Re-apply GH-154858, which was reverted in GH-154992 because it broke the
reference leak buildbots.

``atexit.register(_clear_caches)`` runs on every import of ``typing``, and the
registered handler reaches the module dict through ``_clear_caches.__globals__``.
Any throwaway copy of ``typing`` therefore stays alive until interpreter
shutdown. Two tests create such a copy on each iteration:

- ``InternalsTests.test_collect_parameters`` imports a fresh ``typing``.
- ``CollectionsAbcTests.test_bytestring`` drops ``typing`` from ``sys.modules``
  and re-imports it.

Both now unregister the exit handler of the copy they created.

Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst [new file with mode: 0644]

index 106ffdede6fd4ef332ce8a9cc64904232f87411a..53c8c9fac6946548f7e954ffb425a3cbe89af0af 100644 (file)
@@ -1,4 +1,5 @@
 import annotationlib
+import atexit
 import contextlib
 import collections
 import collections.abc
@@ -6550,6 +6551,14 @@ class NoTypeCheckTests(BaseTestCase):
 class InternalsTests(BaseTestCase):
     def test_collect_parameters(self):
         typing = import_helper.import_fresh_module("typing")
+        # Importing typing registers an internal function named _clear_caches
+        # with atexit. The throwaway module created here installs its own
+        # handler, which holds the module alive and keeps references until
+        # interpreter shutdown even after the test finishes. Each repetition
+        # of this test under -R would therefore leak another module copy. To
+        # avoid this, we unregister the handler once the test is done.
+        self.addCleanup(atexit.unregister, typing._clear_caches)
+
         with self.assertWarnsRegex(
             DeprecationWarning,
             "The private _collect_parameters function is deprecated"
@@ -7719,6 +7728,10 @@ class CollectionsAbcTests(BaseTestCase):
 
         with self.assertWarns(DeprecationWarning):
             from typing import ByteString
+        # Drop the exit handler of this throwaway copy, see the comment in
+        # InternalsTests.test_collect_parameters.
+        self.addCleanup(atexit.unregister, sys.modules["typing"]._clear_caches)
+
         with self.assertWarns(DeprecationWarning):
             self.assertIsInstance(b'', ByteString)
         with self.assertWarns(DeprecationWarning):
index a05d73c29cf95e58e822334bb7a08e7ae55f177e..809c0ff88607a59067cd0061222063eb072bbad0 100644 (file)
@@ -19,6 +19,7 @@ that may be changed without notice. Use at your own risk!
 """
 
 from abc import abstractmethod, ABCMeta
+import atexit
 import collections
 from collections import defaultdict
 import collections.abc
@@ -397,6 +398,11 @@ def _clear_caches():
         cleanup()
 
 
+# Release the LRU caches at shutdown, they otherwise redistribute reference
+# leaks of one extension to types of unrelated ones. See GH-151728.
+atexit.register(_clear_caches)
+
+
 def _tp_cache(func=None, /, *, typed=False):
     """Internal wrapper caching __getitem__ of generic types.
 
diff --git a/Misc/NEWS.d/next/Library/2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst b/Misc/NEWS.d/next/Library/2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst
new file mode 100644 (file)
index 0000000..39c63a1
--- /dev/null
@@ -0,0 +1,4 @@
+Clear the internal :mod:`typing` caches from an exit handler. Previously, an
+extension module that leaked a reference to :mod:`typing` would also keep every
+subscripted type alive past interpreter shutdown, including types owned by
+unrelated extension modules.