Cancellation and cleanup in asyncio

Concurrency in Python services Senior Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

The task is cancelled while sleeping. What's printed?

Python 3.13
import asyncio


async def handler(log: list[str]) -> None:
    try:
        await asyncio.sleep(10)
    finally:
        log.append("released")


async def main() -> None:
    log: list[str] = []
    t = asyncio.create_task(handler(log))
    await asyncio.sleep(0.01)
    t.cancel()
    try:
        await t
    except asyncio.CancelledError:
        log.append("cancelled")
    print(log)


asyncio.run(main())

Options
Hint 1 of 1

What runs as the exception passes through?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A ['released', 'cancelled']

Correct. finally runs as CancelledError passes through, then the awaiting code sees it.

cancel() makes the task's current await raise CancelledError. The handler's finally runs first, cleaning up, and the error then reaches whoever awaits the task.

Why not the others?
  • B ['cancelled']. finally always runs.
  • C ['released']. Awaiting the cancelled task raises CancelledError.
  • D []. Both appends run.

30-second answer

Frameworks typically cancel the request's task, which raises CancelledError at the coroutine's current await. Use try/finally or async with so connections, locks and temp files are released. Don't swallow CancelledError; if you catch it to clean up, re-raise it. Work that must finish even if the client leaves (charging a card already in flight) belongs in a separate task or a job queue, not the request coroutine.

Practise saying it · 30 s. No recording, just a timer

Handle cancellation.

Say your answer out loud, then check which of these points you covered:

  • CancelledError at an await
  • finally / async with
  • Re-raise
  • Must-finish work elsewhere

Deep dive

asyncio.timeout() cancels the inner work and raises TimeoutError outside.

TaskGroup cancels the remaining tasks when one fails.

asyncio.shield protects an inner awaitable from outer cancellation, but the outer await still raises.

Database transactions must roll back on cancellation; context managers do this.

Common mistakes

  • except BaseException: (or a bare except:) that swallows cancellation.
  • Cleanup that awaits forever, blocking shutdown.
  • Doing critical side effects inside a cancellable request task.

Senior take & follow-ups

Cancellation raises at an await; finally/async with for cleanup; re-raise; move must-finish work out.

Follow-up questions

Think of your answer first, then open the hint.

  1. Is CancelledError an Exception?

    Show hint

    No, it subclasses BaseException since Python 3.8, so except Exception won't swallow it.

  2. What does shield do?

    Show hint

    Keeps the inner task running when the outer is cancelled.

Suggest a fix if something here is wrong or out of date.