Description
Bug report
Today I came across the following problem:
import asyncio
ready = asyncio.Event()
async def task_func():
try:
ready.set()
await asyncio.sleep(1)
except asyncio.CancelledError:
# this is required, otherwise a TimeoutError is not created below,
# instead, the timeout-generated CancelledError is just raised upwards.
asyncio.current_task().uncancel()
finally:
# Perform some cleanup, with a timeout
try:
async with asyncio.timeout(0):
await asyncio.sleep(1)
except asyncio.TimeoutError:
pass
async def main():
task = asyncio.create_task(task_func())
await ready.wait()
await asyncio.sleep(0.01)
# the task is now sleeping, lets send it an exception
task.cancel()
# We expect the task to finish cleanly.
await task
asyncio.run(main())
The documentation mentions that sometimes an application may want to suppress CancelledError
. What it fails
to mention is that unless the cancel state is subsequently cancelled with task.uncancel()
, the task remains in a
cancelled 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 a CancelledError
.
It should also be noted that the library asyncio_timeout
on which the asyncio.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
- CPython versions tested on: 3.11
Linked PRs
Metadata
Metadata
Assignees
Projects
Status