You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
asyncio.TaskGroup corrupts uncancel() stack when the parent task replaces the cancelled error #95289
Hello, sorry for my question but I have never seen a * after an except statement. I couldn't find an explanation on the web. What does it mean, please?
In your first example, if you replace pass with raise, you have the expected result.
May be this will help you ?
Hello, sorry for my question but I have never seen a * after an except statement. I couldn't find an explanation on the web. What does it mean, please?
Hi there - it's the new 3.11 ExceptionGroup feature https://peps.python.org/pep-0654/ it's required to handle an issue where multiple errors can happen concurrently. Eg a connection error canceles a group of concurrent operations, and the cancellation error leads to some teardown that also fails. In 3.10 library authors had to either choose an error to raise or wrap them in a list and raise a generic "MultiError"
Hi @graingert, thank you for your response about except * feature.
About the issue, I read the documentation about 'Task groups' and IMHO there is a part about the two cases, in the second paragraph below the exemple:
... At this point, if the body of the async with statement is still active (__aexit__() hasn’t been called yet), the task directly containing the async with statement is also cancelled.
Do you think that your both examples match the situation described ?
Alas, that doesn't fix the original bug report. But here's a variant that does:
diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index 3ca65062ef..0dde87f1ca 100644
--- a/Lib/asyncio/taskgroups.py+++ b/Lib/asyncio/taskgroups.py@@ -61,14 +61,12 @@ async def __aexit__(self, et, exc, tb):
self._base_error is None):
self._base_error = exc
- if et is not None:- if et is exceptions.CancelledError:- if self._parent_cancel_requested and not self._parent_task.uncancel():- # Do nothing, i.e. swallow the error.- pass- else:- propagate_cancellation_error = exc+ if (self._parent_cancel_requested and+ self._parent_task.uncancel() != 0 and+ et is exceptions.CancelledError):+ propagate_cancellation_error = exc+ if et is not None:
if not self._aborting:
# Our parent task is being cancelled:
#
I have other things to do, maybe @graingert and/or @kumaraditya303 can check my fix and turn it into a PR with the two examples as new tests?
Hm, there’s one case where the effect is different from the original: if a CancellationError occurs while _parent_cancel_request is not set. Then the original sets propagate_cancellation_error = exc but the new code does nothing. Does that matter? No tests fail.
That would affect the case where the parent task is cancelled externally — it should propagate the cancellation but with my code it will exit cleanly. This can be fixed and requires a separate test.
Oh, I think I understand why that doesn't matter. In this case we end up returning None from __aexit__() and that causes the interpreter to re-raise the original exception that was being handled. (In fact we may use this fact to simplify the code even further?)
Activity
graingert commentedon Jul 26, 2022
this is also quite easy to trigger in typical task group code:
graingert commentedon Jul 26, 2022
cc @ambv who helped me find this
graingert commentedon Jul 27, 2022
Tagging @pablogsal as potential release-blocker
YvesDup commentedon Jul 27, 2022
Hello, sorry for my question but I have never seen a
*after anexceptstatement. I couldn't find an explanation on the web. What does it mean, please?In your first example, if you replacepasswithraise, you have the expected result.May be this will help you ?
graingert commentedon Jul 27, 2022
Hi there - it's the new 3.11 ExceptionGroup feature https://peps.python.org/pep-0654/ it's required to handle an issue where multiple errors can happen concurrently. Eg a connection error canceles a group of concurrent operations, and the cancellation error leads to some teardown that also fails. In 3.10 library authors had to either choose an error to raise or wrap them in a list and raise a generic "MultiError"
YvesDup commentedon Jul 29, 2022
Hi @graingert, thank you for your response about
except *feature.About the issue, I read the documentation about 'Task groups' and IMHO there is a part about the two cases, in the second paragraph below the exemple:
Do you think that your both examples match the situation described ?
ambv commentedon Jul 29, 2022
@YvesDup, you stopped your quote just before the sentence that describes why what Thomas reported is a bug:
In other words, after leaving
async withwe wantprint(f"{task.cancelling()=} should be 0").kumaraditya303 commentedon Jul 29, 2022
Have you tried this on the edgedb's task group 1 implementation from which the current one is derived from?
Footnotes
https://github.com/edgedb/edgedb/blob/master/edb/common/taskgroup.py ↩
7 remaining items
gvanrossum commentedon Jul 30, 2022
Alas, that doesn't fix the original bug report. But here's a variant that does:
I have other things to do, maybe @graingert and/or @kumaraditya303 can check my fix and turn it into a PR with the two examples as new tests?
gvanrossum commentedon Jul 31, 2022
Hm, there’s one case where the effect is different from the original: if a CancellationError occurs while _parent_cancel_request is not set. Then the original sets propagate_cancellation_error = exc but the new code does nothing. Does that matter? No tests fail.
gvanrossum commentedon Jul 31, 2022
That would affect the case where the parent task is cancelled externally — it should propagate the cancellation but with my code it will exit cleanly. This can be fixed and requires a separate test.
gvanrossum commentedon Aug 1, 2022
Oh, I think I understand why that doesn't matter. In this case we end up returning
Nonefrom__aexit__()and that causes the interpreter to re-raise the original exception that was being handled. (In fact we may use this fact to simplify the code even further?)pythonGH-95289: Always call uncancel() when required
GH-95289: Always call uncancel() when parent cancellation is requested (
pythonGH-95289: Always call uncancel() when parent cancellation is re…
GH-95289: Always call uncancel() when parent cancellation is requested (
kumaraditya303 commentedon Aug 4, 2022
Fixed by #95602