_cleanups = []
+_caches = {}
def _tp_cache(func=None, /, *, typed=False):
original function for non-hashable arguments.
"""
def decorator(func):
- cached = functools.lru_cache(typed=typed)(func)
- _cleanups.append(cached.cache_clear)
+ # The callback 'inner' references the newly created lru_cache
+ # indirectly by performing a lookup in the global '_caches' dictionary.
+ # This breaks a reference that can be problematic when combined with
+ # C API extensions that leak references to types. See GH-98253.
+
+ cache = functools.lru_cache(typed=typed)(func)
+ _caches[func] = cache
+ _cleanups.append(cache.cache_clear)
+ del cache
@functools.wraps(func)
def inner(*args, **kwds):
try:
- return cached(*args, **kwds)
+ return _caches[func](*args, **kwds)
except TypeError:
pass # All real errors (not unhashable args) are raised below.
return func(*args, **kwds)
--- /dev/null
+The implementation of the typing module is now more resilient to reference
+leaks in binary extension modules.
+
+Previously, a reference leak in a typed C API-based extension module could leak
+internals of the typing module, which could in turn introduce leaks in
+essentially any other package with typed function signatures. Although the
+typing package is not the original source of the problem, such non-local
+dependences exacerbate debugging of large-scale projects, and the
+implementation was therefore changed to reduce harm by providing better
+isolation.