Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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-90908: Document asyncio.Task.cancelling() and asyncio.Task.uncancel() #95253
gh-90908: Document asyncio.Task.cancelling() and asyncio.Task.uncancel() #95253
Changes from 6 commits
708cb27
0203e01
4a6a2fe
ad49eb0
4c56381
13515f3
f0a215d
f3bcc6f
26cf287
9296af0
4114a79
50850de
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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.
This part is unchanged, only moved down. IMO cancellation isn't as important as getting other things out of a task. Plus this move allows us to keep cancel-specific methods next to each other,
uncancel
in particular being pretty low-level.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.
This seems to imply that without
uncancel()
,await unrelated_code()
would be cancelled. But even with the most naive implementation possible using only primitives existing in 3.10, ifexcept TimeoutError:
is triggered, the followingawait
will executed just fine. (In Trio this would not be the case, since cancellation there is level-triggered, i.e. once a task is cancelled it stays cancelled and specifically any furtherawait
will immediately be cancelled; but inasyncio
cancellation is edge-triggered, and once the task has regained control, which the context manager can do, it is no longer cancelled.)So why does
uncancel()
exist and need to be called? Because when structured concurrency primitives are nested, they may cancel the same task. The inner primitive(s) of these must let theCancellationError
bubble out, while the outermost one must take the action it promises its caller (e.g. raiseTimeoutError
or raise anExceptionGroup
bundling the error(s) experienced by failed but not cancelled subtasks).I think this means that to make the example meaningful, you'd have to nest two timeout blocks whose timers go off simultaneously. Then the inner one will raise
CancellationError
(so anexcept TimeoutError
will not trigger!) and the outer one will raiseTimeoutError
.It is not a coincidence that
uncancel()
is called in__aexit__()
by both timeout and TaskGroup. The pattern is pretty mucht.cancel()
on some task, and sets an internal flag indicating it did so;__aexit__()
, if the internal flag is set,t.uncancel()
is called, and if it returns a value greater than zero,CancelledError
is (re)raised (by returningNone
from__aexit__()
!), else some other action is taken.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.
This seems to imply that the effect of
cancel()
+uncancel()
is a no-op, but I'm not sure that's always the case. Honestly after several attempts I'm still not sure howcancel()
and__step()
interacted even in 3.10. :-( But it seems at least possible that.cancel()
sets_must_cancel
which causes__step()
to throw aCancelledError
into the coroutine even ifuncancel()
is called immediately aftercancel()
.See also the comment added to
cancel()
starting with "These two lines are controversial."(Also 7d611b4)
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.
The previous docstring was invalid, we actively don't want for user code to call
uncancel()
when catching CancelledError.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 like you to look at this test and tell me if you think anything here (esp. the comments!) is not factual.
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.
Canceling the stimulus (timeouts), or guarding against cancelling after calling uncancel() (TaskGroup and Runner) I think is a sixth rule
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 commit 7d611b4 changes the behavior again.
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.
"Those checks" == (2), (4), (5), right? Because (1) and (3) don't describe "checks".
Other than that nit, these comments seem correct, and the code looks so too (but I'm not taking money :-).
(Another nit: the huge comment interrupts the logic. Maybe move it to the top of the test function?)