Skip to content

Catching CancelledError requires use of task.uncancel() #102780

Closed
@kristjanvalur

Description

@kristjanvalur

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

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions