We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Concurrency with Threads and asyncio step 30 of 56
Run Async Cleanup Without Hiding the Real Error
Write a helper that awaits body() and then always awaits cleanup(), even
when the body fails or is cancelled. The caller gets the body's value or its
exception. A failing cleanup must leave the body's error in place, and a stuck
cleanup must give up after a time limit.
async def with_cleanup[T](
body: Callable[[], Awaitable[T]],
cleanup: Callable[[], Awaitable[None]],
*,
cleanup_timeout: float,
) -> T
For example, if body() raises ValueError("body failed") and cleanup()
then raises RuntimeError, the caller gets the ValueError, with the note
"cleanup failed" attached.
What to write
Await body(). Then await cleanup() in the same task, inside
asyncio.timeout(cleanup_timeout). Finish according to this table:
| If the body... | and cleanup finishes in time | and cleanup raises or runs out of time |
|---|---|---|
| returns a value | return that value | return that value |
| raises, or is cancelled | re-raise the same exception | add one note, then re-raise the same exception |
Add the note with exc.add_note(...), using the two strings the starter
defines:
CLEANUP_TIMED_OUTwhen your cleanup timer ran out. This includes a cleanup that caught the timer's cancellation and then returned normally.CLEANUP_FAILEDwhen cleanup raised anExceptionbefore your timer ran out. ATimeoutErrorthat cleanup raises by itself counts as a failure.- No note when cleanup finished in time.
To tell your timer apart from cleanup's own TimeoutError, keep the timeout
in a variable, scope = asyncio.timeout(cleanup_timeout). Then
scope.expired() is True only when your timer ran out.
Also follow these rules:
- Catch
BaseExceptionaround the body, so cleanup runs after a cancellation too. After cleanup, re-raise the exception with a bareraise. - Around cleanup, catch only
Exception. If the task is cancelled again during cleanup, thatCancelledErrormust propagate. - Run everything in the caller's task. The report counts any extra task that is still running.
What the driver reports
Keep solve as it is. Each scenario picks a body that returns 42 or raises,
and a cleanup that succeeds, raises, raises TimeoutError or hangs until your
timer cancels it. Some scenarios also cancel the task partway through the
body. The report has these keys:
log: the steps the body and cleanup ran, in order.outcome:"ok", or the name of the exception the caller saw.message: that exception's message, or"".value: the body's return value, or-1if the caller saw an exception.notes: the notes on that exception, or[].pending: tasks other than the driver still running at the end. It must be0.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.