-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
Catching CancelledError requires use of task.uncancel() #102780
Comments
This should be treated as a documentation issue. The mechanism is essential for task groups. |
Yes, I wasn't quite sure about that. |
If you can propose a fix to the timeout code that would be great, I wasn’t aware of the issue there. |
This is the code in question: async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
assert self._state in (_State.ENTERED, _State.EXPIRING)
if self._timeout_handler is not None:
self._timeout_handler.cancel()
self._timeout_handler = None
if self._state is _State.EXPIRING:
self._state = _State.EXPIRED
if self._task.uncancel() == 0 and exc_type is exceptions.CancelledError:
# Since there are no outstanding cancel requests, we're
# handling this.
raise TimeoutError
elif self._state is _State.ENTERED:
self._state = _State.EXITED
return None The test for the I guess I could at least provide a PR for the docs, suggest that if the user chooses to suppress a |
Unfortunately @asvetlov is busy. Since I reviewed it I could try to have a look but it's been a while since I understood that code. Comparison to how and why task groups use uncancel() might help. The proposed doc update sounds good. |
I think I know what is going on. Because exceptions are delivered asynchronously, the intent here is to guard against a race condition: A timeout expires, a cancel is scheduled to be delivered. At the same time, a proper cancel is also issued. We want the proper cancel to propagate and not be turned into a timeout error. I think this is fixable, at least in the majority of cases, by comparing the cancel counter before and after entering. I'll see if I can make that happen without making too much rubble. On the other hand, I think this also indicates what I think is a shortcoming with the asyncio api: There is currently no mechanism to send a general exception to a task. There is only In I have previously stopped short working with asyncio wanting to send a custom exception and not finding it possible. I would suggest that such an api be added. Even A complication in asyncio vs stackless is that in the latter, the exceptions could be raised synchronously. There was no race, where different exceptions might be sent to a tasklet, each overriding the previous. In asyncio, this could be accomplished by considering the Implementing a timeout by simply delivering a custom Anyway, just a few thoughts I wanted to get out of my head :) |
Seems worth looking into where's the difference between the two. I'll take a look at this and PR in more detail in next week. |
This is at least worth a discussion, but I'd rather not continue in this thread. Could you create a new thread with the same thoughts? Given how much we've been struggling with cancellation I agree it deserves more consideration. |
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is set in the `__cause__` field rather than in the `__context__` field. Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
…2815) Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is set in the `__cause__` field rather than in the `__context__` field. (cherry picked from commit 04adf2d) Co-authored-by: Kristján Valur Jónsson <sweskman@gmail.com> Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Thanks for the fix, Kristjan. (And welcome back!) I'm backporting this to 3.11 as a bugfix. |
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is set in the `__cause__` field rather than in the `__context__` field. (cherry picked from commit 04adf2d) Co-authored-by: Kristján Valur Jónsson <sweskman@gmail.com> Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Thanks. I'm never far off, and when I notice curious and strange behaviour in Python, I try to help fix it. My first core fix in many years was in #95207 |
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is set in the `__cause__` field rather than in the `__context__` field. Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is set in the `__cause__` field rather than in the `__context__` field. Co-authored-by: Guido van Rossum <gvanrossum@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
…3321) * Pull in asyncio.timeout implementation from Python 3.11.3 * Addresses issue from python/cpython#102780
Bug report
Today I came across the following problem:
The documentation mentions that sometimes an application may want to suppress
CancelledError
. What it failsto mention is that unless the cancel state is subsequently cancelled with
task.uncancel()
, the task remains in acancelled state, and context managers such as
asyncio.timeout
will not work as designed. However,task.uncancel()
is not supposed to be called by user code.
I think the documentation should mention this use case for
task.uncancel()
, particularly in the context of catching (and choosing to ignore)CancelledError
.This could also be considered a bug in
asyncio.timeout
. It prevents the use of the Timeout context manager within cleanup code, even if the intention is not to ignore aCancelledError
.It should also be noted that the library
asyncio_timeout
on which theasyncio.timeout
implementation is based, does not have this problem. Timeouts continue to work as designed, even if the task has been previously cancelled.Your environment
Linked PRs
The text was updated successfully, but these errors were encountered: