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-112202: Ensure that condition.notify() succeeds even when racing with Task.cancel() #112201
gh-112202: Ensure that condition.notify() succeeds even when racing with Task.cancel() #112201
Conversation
I'm reviewing this, but (as usual with locking changes) it is hard work (thanks for helping out here!). Plus I need clarity about the relationship with gh-111694. |
Turned it into a draft until gh-111694 is merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've put off reviewing this PR until gh-111694 lands, but I had already started some doc nits, so here they are. Also note I changed this to Draft mode -- please undraft it once the other PR is merged (into main, and here).
|
||
def _notify(self, n): | ||
idx = 0 | ||
for fut in self._waiters: | ||
if idx >= n: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the docstring for notify_all()
below mentions threads (twice). That should probably be changed to tasks. (Or coroutines, like above? Though IMO that should also be tasks -- in practice all coroutines are wrapped by tasks, and tasks are the unit of control that users are encouraged to think in terms of.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the mention of "coroutines" is a relic from the very old days.
The locks.py discusses coroutines in a lot of the docstrings where "tasks" are more appropriate. scheduling works on Task objects, not coroutines. I can change it wholesale, do I use "Task" or "task" when doing so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use "task" -- it doesn't really matter whether they are technically instances of asyncio.Task
, and in fact IIRC even loops may override create_task()
to return instances of some other class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I take it you approve of me going over the other inline docs/comments and making that correction, I'll do that in a separate commit.
Lib/asyncio/locks.py
Outdated
This method wakes up at most n of the coroutines waiting for the | ||
This method wakes up n of the coroutines waiting for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as in the docs above.
0939710
to
d7be0d0
Compare
I've rebased and re-opened this pr, will address the comments presently. |
Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
7f17d65
to
c8e6b13
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, we're close to merging this now.
I wonder if we should use "awakened" consistently instead of "awoken"?
PS. I will admit to not carefully reading through the tests, I trust you have got this right.
Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst
Outdated
Show resolved
Hide resolved
|
||
def _notify(self, n): | ||
idx = 0 | ||
for fut in self._waiters: | ||
if idx >= n: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use "task" -- it doesn't really matter whether they are technically instances of asyncio.Task
, and in fact IIRC even loops may override create_task()
to return instances of some other class.
Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
I think you are right, I fixed it.
I certainly hope so :). I made sure they failed without the fix, it's easy to test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two doc nits, then I'll merge.
Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst
Outdated
Show resolved
Hide resolved
Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
Thank you for your help, Guido! |
…cing with Task.cancel() (python#112201) Also did a general cleanup of asyncio locks.py comments and docstrings.
…cing with Task.cancel() (python#112201) Also did a general cleanup of asyncio locks.py comments and docstrings.
If
Condition.notify()
raises an exception that could have occurred after the task was selected to be woken up, wake up another task instead, if available.This ensures that the typical case, of waking up one task to handle something, will succeed even if the candidate task
is cancelled at the same time or otherwise wakes up with an error.
This may result in a spurious wakeup of a waiting task, which is what the Condition Variable protocol specifies, precisely because waking up tasks conservatively (rather wake up too many than too few) is the safest and simplest to implement, and missing wakeups are serious (and cause deadlocks), while extra wakeups are easily handled by application code.
IMHO trying to guarantee that we never perform any extra wakeups would complicate the code too much and be too hard to verify, particularly since that is not a guarantee that Condition Variables are supposed to provide.
The PR includes
condition.wait
after having released the lock will result in a second task being awoken, if available📚 Documentation preview 📚: https://cpython-previews--112201.org.readthedocs.build/