Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-87634: remove locking from functools.cached_property #101890

Merged
merged 5 commits into from
Feb 23, 2023

Conversation

carljm
Copy link
Member

@carljm carljm commented Feb 13, 2023

In https://discuss.python.org/t/finding-a-path-forward-for-functools-cached-property/23757/26, by far the most favored option for dealing with #87634 is to simply remove the locking behavior from cached_property. For the sake of moving things forward, here's a PR for discussion which does that.

Even if we do this, there are several options to consider here:

  1. We could do this more slowly, by just warning in the docs in 3.12 (and 3.13?) that this is going to happen, and then actually doing it in 3.13 (or 3.14). If that's preferred, I'm happy to push a PR with just the docs change for now. Personally I think there's a lot of benefit to having the lock-free cached_property available sooner, and I'm not sure how much benefit the extra time gives anyone: there isn't a runtime deprecation warning that this gives people more time to see, and this isn't a difficult change to adapt to: the current locking version of cached_property is <50 lines of self-contained pure Python code; any project that wants it can easily just take it. And there's already a third-party library that includes it. At least one core developer in the discourse thread suggested that this is changing an undocumented behavior detail, and can just be done without a lengthy deprecation.
  2. We could keep the current locking version in the stdlib under a different name (e.g. locking_cached_property), in hopes that in the future it will be fixed to use a more fine-grained locking strategy. That makes it even simpler to adapt to the change.

Lib/functools.py Outdated Show resolved Hide resolved
Lib/functools.py Show resolved Hide resolved
@rhettinger rhettinger removed their request for review February 15, 2023 03:33
is removed, because it locked across all instances of the class, leading to high
lock contention. This means that a cached property getter function could now
occasionally run more than once for a single instance, if two threads race. For
most simple cached properties (e.g. those that are idempotent and simply
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "occasionally" is not necessary -- we already have the "could" and "if".

The *cached_property* does not prevent a possible race condition in
multi-threaded usage. The getter function could run more than once on the
same instance, with the latest run setting the cached value. If the cached
property is idempotent or otherwise not harmful to (in rare cases) run more
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"(in rare cases)" seems to mean that it's rare for a multiply executed getter to be okay -- I would expect the opposite to be true. If you agree, just remove the parenthetical.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent was to say that the "running more than once on an instance" is a rare case. But the wording could be confusing, and doesn't seem necessary, so I'll remove it.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

* main: (76 commits)
  Fix syntax error in struct doc example (python#102160)
  pythongh-99108: Import MD5 and SHA1 from HACL* (python#102089)
  pythonGH-101777: `queue.rst`: use 2 spaces after a period to be consistent. (python#102143)
  Few coverage nitpicks for the cmath module (python#102067)
  pythonGH-100982: Restrict `FOR_ITER_RANGE` to a single instruction to allow instrumentation. (pythonGH-101985)
  pythongh-102135: Update turtle docs to rename wikipedia demo to rosette (python#102137)
  pythongh-99942: python.pc on android/cygwin should link to libpython per configure.ac (pythonGH-100356)
  pythongh-95672 fix typo SkitTest to SkipTest (pythongh-102119)
  pythongh-101936: Update the default value of fp from io.StringIO to io.BytesIO (pythongh-102100)
  pythongh-102008: simplify test_except_star by using sys.exception() instead of sys.exc_info() (python#102009)
  pythongh-101903: Remove obsolete undefs for previously removed macros Py_EnterRecursiveCall and Py_LeaveRecursiveCall (python#101923)
  pythongh-100556: Improve clarity of `or` docs (python#100589)
  pythongh-101777: Make `PriorityQueue` docs slightly clearer (python#102026)
  pythongh-101965: Fix usage of Py_EnterRecursiveCall return value in _bisectmodule.c (pythonGH-101966)
  pythongh-101578: Amend exception docs (python#102057)
  pythongh-101961 fileinput.hookcompressed should not set the encoding value for the binary mode (pythongh-102068)
  pythongh-102056: Fix a few bugs in error handling of exception printing code (python#102078)
  pythongh-102011: use sys.exception() instead of sys.exc_info() in docs where possible (python#102012)
  pythongh-101566: Sync with zipp 3.14. (pythonGH-102018)
  pythonGH-99818: improve the documentation for zipfile.Path and Traversable (pythonGH-101589)
  ...
@@ -959,15 +959,12 @@ def __isabstractmethod__(self):
### cached_property() - computed once per instance, cached as attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should also be changed. @carljm

Copy link
Member Author

@carljm carljm Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an absolute guarantee in the presence of multi-threading race conditions, but this is still a generally accurate description of the usual behavior of a cached_property. What alternative wording would you suggest for this one-line comment that is both clear and concise? Note the public-facing documentation already contains a full and clear description of the behavior. (To be clear this is a genuine question, I'm open to changing this comment if you have a good suggestion, I'm just not sure what a clear and concise wording would be, and I think it might be OK to leave it as is.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

### cached_property() - property result cached as instance attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me! I've rolled that change into #106380 where I'm already touching cached_property.

JelleZijlstra pushed a commit to JelleZijlstra/cpython that referenced this pull request Sep 10, 2024
…GH-101890)

Remove the undocumented locking capabilities of functools.cached_property.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants