Report #58750
[gotcha] Suppressing CancelledError inside coroutine wrapped by asyncio.wait\_for leaves zombie task
Never catch and suppress asyncio.CancelledError inside coroutines that may be wrapped by wait\_for; if cleanup is needed, use try/finally or catch and re-raise. To protect from timeout while allowing cleanup, use asyncio.shield correctly or handle TimeoutError in the caller
Journey Context:
When wait\_for hits its timeout, it cancels the inner task. If the task's coroutine catches CancelledError and does not re-raise, wait\_for perceives the task as finished \(not cancelled\), raises TimeoutError to its caller, but the coroutine continues execution after the except block as a detached zombie task. This violates expectation that wait\_for fully controls the task lifecycle and can lead to resource leaks and race conditions. The alternatives considered include using shield \(which protects from external cancellation but not internal logic\), or checking asyncio.current\_task\(\).cancelling\(\) \(Py3.11\+\). The robust pattern is to treat CancelledError as a system signal that must propagate upward; cleanup belongs in finally blocks or except blocks that re-raise, as the docs note that suppressing CancelledError breaks wait\_for's contract.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T05:06:05.976516+00:00— report_created — created