We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Orientation and the Gate step 12 of 13
Placement 5/6: async cancellation
Placement diagnostic, 5 of 6. About seven minutes. If this one is opaque, T9 (Concurrency Without Regret) is where you start.
Cancellation in asyncio is not a flag you poll. task.cancel() schedules a
CancelledError to be thrown into the coroutine at its current suspension
point — which means it can only ever arrive at an await, and it behaves
like any other exception once it does: your except clauses see it, your
finally blocks run, and if you swallow it the task completes normally.
The job coroutine is given, and it is instrumented so you can watch all of
that happen. Write the driver:
async def drive(reraise: bool, ticks: int) -> list[str]:
-
Start
job(log, reraise)as a task — it must run concurrently with you, not be awaited inline. -
Yield control to the event loop exactly
tickstimes. - Cancel the task.
-
Await it, and append one final entry to the log:
"propagated"if aCancelledErrorreached you, otherwise"returned:" + result. - Return the log.
solve is given: asyncio.run(drive(reraise, ticks)).
The four regimes the tests walk through
ticks=0 — you cancel before the task has ever been scheduled. job never
runs, the log has no "start", no "cleanup" runs, and you get
["propagated"]. A task cancelled before its first step does not execute
its finally blocks, because there is no frame to unwind. This is why
“acquire the resource inside the task” and “acquire it before creating the
task” are different designs.
ticks=1..4 — cancellation lands mid-flight. job catches it, logs
"caught", and its finally logs "cleanup". Then reraise decides
everything:
-
reraise=True— theCancelledErrorpropagates, the task ends up cancelled, and yourawait taskraises. Log ends"propagated". -
reraise=False—jobreturns"swallowed"instead. The task completes successfully. Yourawait taskreturns a value and never raises. Log ends"returned:swallowed".
ticks>=5 — job has already finished by the time you call cancel().
Cancelling a completed task is a no-op that returns False, and await task
hands you "finished".
Why swallowing is the bug
In the reraise=False regime, job was told to stop and did not. Its caller
cannot tell the difference between “completed the work” and “was cancelled
and lied about it”. Scale that up: a TaskGroup shutting down after a
sibling failure waits forever on the one task that catches CancelledError
and carries on; a server draining connections never drains. The rule is
absolute — catch CancelledError only to clean up, then re-raise. If you
need a value out of a cancelled operation, put it somewhere the caller can
reach, do not suppress the cancellation to return it.
Note also that except Exception does not catch this. Since Python 3.8
CancelledError inherits from BaseException precisely so that the
ubiquitous except Exception: log.exception(...) does not eat it. except BaseException and a bare except: do — which is one concrete reason not to
write either.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.