From f42f407b11b48e9777bd90c266e3654cc8db9026 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 31 Jul 2026 19:16:12 +0200 Subject: [PATCH] gh-151728: Clear the typing caches at interpreter shutdown 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 | 13 +++++++++++++ Lib/typing.py | 6 ++++++ .../2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst | 4 ++++ 3 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 106ffdede6fd4ef..53c8c9fac694654 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1,4 +1,5 @@ import annotationlib +import atexit import contextlib import collections import collections.abc @@ -6550,6 +6551,14 @@ class F: 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 @@ def test_bytestring(self): 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): diff --git a/Lib/typing.py b/Lib/typing.py index a05d73c29cf95e5..809c0ff88607a59 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -19,6 +19,7 @@ """ 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 index 000000000000000..39c63a1aea3193a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-29-11-05-00.gh-issue-151728.Kq3vTn.rst @@ -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.