Skip to content

Commit 30f839b

Browse files
committed
Remove cached_method locking logic
Although this logic tried to avoid lock contention, there were still scenarios under which it was possible and could negatively impact users. Removal and adjustment of the documentation puts responsibility for locking/safety onto users, similar to other parts of the stdlib.
1 parent 8ff6b31 commit 30f839b

2 files changed

Lines changed: 13 additions & 22 deletions

File tree

Doc/library/functools.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ The :mod:`!functools` module defines the following functions:
152152
The *cached_method* does not prevent all possible race conditions in
153153
multi-threaded usage. The function could run more than once on the
154154
same instance, with the same inputs, with the latest run setting the cached
155-
value. However, initialization of the cached method, which happens lazily on
156-
first access, is itself threadsafe.
155+
value. The per-instance cache is lazily initialized on first access (via the
156+
descriptor protocol), so parallel access on a single instance can race to
157+
initialize.
157158

158159
This decorator requires that the each instance supports weak references.
159160
Some immutable types and slotted classes without ``__weakref__`` as one of

Lib/functools.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,8 +1217,6 @@ class _cached_method:
12171217
"""
12181218
def __init__(self, func, /, maxsize=None, typed=False):
12191219
self._function_table = {}
1220-
# we need a lock when initializing per-instance caches
1221-
self._cache_init_lock = RLock()
12221220

12231221
self._maxsize = maxsize
12241222
self._typed = typed
@@ -1242,27 +1240,19 @@ def _get_or_create_cached_func(self, instance):
12421240

12431241
instance_id = id(instance)
12441242

1245-
# first try to retrieve the cached func without locking (thus avoiding any
1246-
# unnecessary contention when there is a value), but then retry
1247-
# under a lock to actually provide safety such that two parallel threads won't
1248-
# construct distinct caches simultaneously
12491243
try:
12501244
ref, cached_func = self._function_table[instance_id]
12511245
except KeyError:
1252-
with self._cache_init_lock:
1253-
try:
1254-
ref, cached_func = self._function_table[instance_id]
1255-
except KeyError:
1256-
ref = weakref.ref(
1257-
instance,
1258-
_cached_method_weakref_callback(
1259-
self._function_table, instance_id
1260-
),
1261-
)
1262-
cached_func = _wrap_unbound_cached_method(
1263-
ref, self.func, self._maxsize, self._typed
1264-
)
1265-
self._function_table[instance_id] = ref, cached_func
1246+
ref = weakref.ref(
1247+
instance,
1248+
_cached_method_weakref_callback(
1249+
self._function_table, instance_id
1250+
),
1251+
)
1252+
cached_func = _wrap_unbound_cached_method(
1253+
ref, self.func, self._maxsize, self._typed
1254+
)
1255+
self._function_table[instance_id] = ref, cached_func
12661256

12671257
return cached_func
12681258

0 commit comments

Comments
 (0)