Skip to content
Preptima
hardConceptScenarioMidSeniorStaff

An asyncio call times out and you handle the TimeoutError, but the background task keeps running and mutates shared state a few seconds later. What happened, and how do you make the timeout actually stop the work?

A timeout requests cancellation; it does not stop code. CancelledError is raised at the task next await point, so a task inside blocking code, shielded, in an executor thread, or swallowing the exception keeps running and commits its side effects late. Fix by re-raising CancelledError, keeping blocking calls off the loop, and using asyncio.timeout with a TaskGroup.

4 min readUpdated 2026-08-16Target archetype: Big Tech, Product Startup, Enterprise Captive

Answer

Short answer

The timeout worked; the cancellation did not. asyncio.wait_for cancels the task it is waiting on, but cancellation in asyncio is cooperative: it schedules a CancelledError to be raised at the task's next await point. A task that is not currently at an await point, or that catches the exception and carries on, keeps running to completion — and commits its side effects long after the caller gave up.

Was this answer helpful?

Likely follow-ups

  • Your task catches CancelledError to run cleanup. What must it do at the end of that handler, and why?
  • The blocking work is a synchronous database driver call in run_in_executor. Can you cancel it at all?
  • What does "Task was destroyed but it is pending!" mean when you see it in the logs?
  • How would you prove in a test that the task actually stopped, rather than that the caller stopped waiting?
  • When is asyncio.shield the correct tool rather than a bug?

Related questions

Further reading