gh-116720: Fix corner cases of taskgroups#117407
Conversation
e97f22a to
5912d70
Compare
|
@arthur-tacca, what do you think of this PR? Does it pass all your tests? Do you want to contribute in any way (e.g. by converting your example programs into unittests)? |
|
@agronholm I wonder if you could review this PR? (It's in draft mode because there are no tests or doc yet, but the changes to taskgroups.py and tasks.py are as I plan to keep them.) It might affect anyio because of two things: (a) Better support for nested taskgroups; (b) change to uncancel() to reset the internal "must cancel" flag when the pending cancellation count reaches zero. |
|
@Tinche Same question for you -- you might have an opinion on the changes that I'm proposing to make to asyncio task and task group behavior here. |
Sure, I can take a look. |
|
It's hard to definitively say if this will affect AnyIO, but I did run the task group tests against this PR and they passed. The only place where AnyIO looks at this flag is in |
|
Sorry, which flag is that? ‘cancelling()’ or ‘_must_cancel’? |
|
| if self._num_cancels_requested > 0: | ||
| self._num_cancels_requested -= 1 | ||
| if self._num_cancels_requested == 0: | ||
| self._must_cancel = False |
There was a problem hiding this comment.
Shouldn't this also clear the cancel message?
There was a problem hiding this comment.
I doubt it -- self._cancel_message is never cleared, and it's never used unless self._must_cancel is set.
There was a problem hiding this comment.
Okay – I was just confused by that Py_CLEAR() in the C version, but this explains it.
There was a problem hiding this comment.
Oh, you're right, that CLEAR is unnecessary there too. :-)
This fixes the one failing test in test_timeouts. Surprisingly, it doesn't break any other asyncio tests.
Co-Authored-By: @Tinche
Co-Authored-By: @arthur-tacca
Co-Authored-By: @arthur-tacca
|
Still to do are docs updates for uncancel and for task groups. Also, I'd love help with shortening the news blurb. |
|
@willingc Would you be my reviewer for this PR? I feel particularly challenged by the various documentation updates -- this is a pretty subtle improvement (given how long it's taken before someone reported the issue :-) and I'm struggling describing the changes and the end state appropriately in various places:
I'd say that currently the Changelog news item is probably too long, and there's too much duplication between all three forms of documentation. @arthur-tacca: I haven't heard from you on the issue in a while. Your feedback on any part of this PR would be much appreciated. I plan to give you credit by adding |
|
@gvanrossum Sorry for ghosting you. I had some limited time for open source between jobs (which I actually planned to spend mainly on something else), which has run out and it's now hard for me to post even short comments like this one. Super-brief review: Your code changes look good to me. As I said in an earlier comment, I think it's good that you've just moved (rather than duplicated) the Credit: Thanks for crediting me, that's much appreciated. I don't think my contributions are significant enough to need a CLA but in any case I have signed one (for a small change to typing extensions). Changelog:
Docs changes: "Task groups are careful not to drop outside cancellations when they collide with a cancellation internal to the task group." I must admit, I would struggle to understand what this means if I hadn't been involved in this issue. But that isn't very helpful since I don't have a better suggestion to give. |
Lib/asyncio/taskgroups.py
Outdated
| self._errors.append(exc) | ||
|
|
||
| if self._errors: | ||
| # If the parent task is being cancelled from the outside, |
There was a problem hiding this comment.
| # If the parent task is being cancelled from the outside, | |
| # If the parent task is being cancelled from the outside of the taskgroup, |
willingc
left a comment
There was a problem hiding this comment.
@gvanrossum Overall, this looks good. I made a few suggestions on wording, but the existing text is also fine. Thanks!
Lib/asyncio/taskgroups.py
Outdated
|
|
||
| if self._errors: | ||
| # If the parent task is being cancelled from the outside, | ||
| # re-cancel it, while keeping the cancel count stable. |
There was a problem hiding this comment.
| # re-cancel it, while keeping the cancel count stable. | |
| # un-cancel and re-cancel the parent task, which will keep the cancel count stable. |
Doc/library/asyncio-task.rst
Outdated
| the cancellation state. | ||
|
|
||
| When this method decrements the cancellation count to zero, | ||
| if a previous :meth:`cancel` call had arranged for a |
There was a problem hiding this comment.
| if a previous :meth:`cancel` call had arranged for a | |
| the method checks if a previous :meth:`cancel` call had arranged for |
Doc/library/asyncio-task.rst
Outdated
|
|
||
| When this method decrements the cancellation count to zero, | ||
| if a previous :meth:`cancel` call had arranged for a | ||
| :exc:`CancelledError` to be thrown into the task, |
There was a problem hiding this comment.
| :exc:`CancelledError` to be thrown into the task, | |
| :exc:`CancelledError` to be thrown into the task. |
Doc/library/asyncio-task.rst
Outdated
| When this method decrements the cancellation count to zero, | ||
| if a previous :meth:`cancel` call had arranged for a | ||
| :exc:`CancelledError` to be thrown into the task, | ||
| but this hadn't been done yet, that arrangement will be |
There was a problem hiding this comment.
| but this hadn't been done yet, that arrangement will be | |
| If this hadn't been done yet, that arrangement will be |
| (Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.) | ||
|
|
||
| * Improved behavior of :class:`asyncio.TaskGroup` when an outside cancellation | ||
| collides with an internal cancellation. For example, when two task groups |
Also switch to consistently using internal/external instead of inside/outside.
willingc
left a comment
There was a problem hiding this comment.
Looks good. Thanks everyone.
|
@gvanrossum would you mind if I copied your solution into quattro to serve as a backport of sorts, for 3.11 and 3.12? Quattro is Apache 2 licensed, that's compatible right? |
That should be absolutely fine -- if I understand the PSF license correctly, all you need to do is add this text somewhere and add a copy of the PSF license for 3.13 somewhere. |
This prevents external cancellations of a task group's parent task to be dropped when an internal cancellation happens at the same time. Also strengthen the semantics of uncancel() to clear self._must_cancel when the cancellation count reaches zero. Co-Authored-By: Tin Tvrtković <tinchester@gmail.com> Co-Authored-By: Arthur Tacca
uncancel()behavior (reset_must_cancel)uncancel()