[issue43468] functools.cached_property incorrectly locks the entire descriptor on class instead of per-instance locking
Raymond Hettinger
report at bugs.python.org
Wed Aug 4 01:19:26 EDT 2021
Raymond Hettinger <raymond.hettinger at gmail.com> added the comment:
Here's the latest effort:
---------------------------------------------------------------
def __get__(self, instance, owner=None):
if instance is None:
return self
if self.attrname is None:
raise TypeError(
"Cannot use cached_property instance without calling __set_name__ on it.")
try:
cache = instance.__dict__
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
# Quickly and atomically determine which thread is reponsible
# for doing the update, so other threads can wait for that
# update to complete. If the update is already done, don't
# wait. If the updating thread is reentrant, don't wait.
key = id(self)
this_thread = get_ident()
with self.updater_lock:
val = cache.get(self.attrname, _NOT_FOUND)
if val is not _NOT_FOUND:
return val
wait = self.updater.setdefault(key, this_thread) != this_thread
# ONLY if this instance currently being updated, block and wait
# for the computed result. Other instances won't have to wait.
# If an exception occurred, stop waiting.
if wait:
with self.cv:
while cache.get(self.attrname, _NOT_FOUND) is _NOT_FOUND:
self.cv.wait()
val = cache[self.attrname]
if val is not _EXCEPTION_RAISED:
return val
# Call the underlying function to compute the value.
try:
val = self.func(instance)
except Exception:
val = _EXCEPTION_RAISED
# Attempt to store the value
try:
cache[self.attrname] = val
except TypeError:
# Note: we have no way to communicate this exception to
# threads waiting on the condition variable. However, the
# inability to store an attribute is a programming problem
# rather than a runtime problem -- this exception would
# likely occur early in testing rather than being runtime
# event triggered by specific data.
msg = (
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
# Now that the value is stored, threads waiting on the condition
# variable can be awakened and the updater dictionary can be
# cleaned up.
with self.updater_lock:
self.updater.pop(key, None)
cache[self.attrname] = _EXCEPTION_RAISED
self.cv.notify_all()
if val is _EXCEPTION_RAISED:
raise
return val
----------
_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue43468>
_______________________________________
More information about the Python-bugs-list
mailing list