This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: asyncio.CancelledError should contain more information on cancellations
Type: behavior Stage: resolved
Components: asyncio Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, dreamsorcerer, yselivanov
Priority: normal Keywords:

Created on 2021-09-04 13:17 by dreamsorcerer, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (4)
msg401045 - (view) Author: Sam Bull (dreamsorcerer) * Date: 2021-09-04 13:17
There are awkward edge cases caused by race conditions when cancelling tasks which make it impossible to reliably cancel a task.

For example, in the async-timeout library there appears to be no way to avoid suppressing an explicit t.cancel() if that cancellation occurs immediately after the timeout.

In the alternative case where a cancellation happens immediately before the timeout, the solutions feel dependant on the internal details of how asynico.Task works and could easily break if the behaviour is tweaked in some way.

What we really need to know is how many times a task was cancelled as a cause of the CancelledError and ideally were the cancellations caused by us.

The solution I'd like to propose is that the args on the exception contain all the messages of every cancel() call leading up to that exception, rather than just the first one.

e.g. In these race conditions e.args would look like (None, SENTINEL), where SENTINEL was sent in our own cancellations. From this we can see that the task was cancelled twice and only one was caused by us, therefore we don't want to suppress the CancelledError.

For more details to fully understand the problem:
https://github.com/aio-libs/async-timeout/pull/230
https://github.com/aio-libs/async-timeout/issues/229#issuecomment-908502523
https://github.com/aio-libs/async-timeout/pull/237
msg413394 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2022-02-17 09:34
After TaskGroup merging, the second `cancel()` call returns `False` and doesn't initiate the actual cancellation if it was scheduled by the first `cancel()` call.

I believe it "fixes" async-timeout: the first canceller wins.
msg413549 - (view) Author: Sam Bull (dreamsorcerer) * Date: 2022-02-19 13:23
I think there's still a problem, in that the user still expects a task to be cancelled in the example previously: https://github.com/aio-libs/async-timeout/issues/229#issuecomment-908502523

If we encounter the race condition where the timeout cancels the task and then the user cancels the task, then we still have the case that async-timeout swallows the cancellation and the task will run forever. This would basically require the user to check everytime they want to cancel the task, with something awkward like:

```
while not task.cancel() and not task.cancelled():
    await asyncio.sleep(0)
```

I think this change is still necessary, but rather than adding multiple values to e.args, we can use the new ExceptionGroup to raise multiple CancelledErrors. So, each call of task.cancel() will create a new CancelledError, and then all of those CancelledErrors will get raised together.

For async-timeout, we can then just catch the CancelledError with our sentinel and raise a TimeoutError, while reraising any other CancelledErrors.
msg415915 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2022-03-23 23:18
#46829 deprecates cancellation messages.
#46771 implements timeout context manager based on previously added cancellation counter and task.uncancel()

I think this issue should be closed.
History
Date User Action Args
2022-04-11 14:59:49adminsetgithub: 89261
2022-03-23 23:18:35asvetlovsetstatus: open -> closed
resolution: rejected
messages: + msg415915

stage: resolved
2022-02-19 13:23:12dreamsorcerersetmessages: + msg413549
2022-02-17 09:34:03asvetlovsetmessages: + msg413394
2021-09-04 13:17:43dreamsorcerercreate