cancel() is a synchronous function, only queueing a request to cancel the nursery. It won't propagate until the code calling
cancel() yields execution.
Why was it done that way? Would it have been possible to just have cancel() throw an exception to immediately stop the child task? I'm thinking this might lead to surprising behavior because (IIUC) calling cancel won't interrupt the thread and so subsequent code will still get executed, e.g.
if not should_launch_missles then
Good question-- there are several things to unpack here, bear with me.
It's possible to have had an await_cancel(), which doesn't return until after all children were resumed with the cancel exception. However:
* as mentioned in the 2nd part, there are advantages to keeping API calls synchronous, especially regarding orchestration of tasks (e.g. you could cancel multiple nurseries atomically, or cancel in combination with other operations that need to be atomic)
* it may be a long wait until you get control back. Tasks are allowed to opt-in to asynchronous finalization (not discussed or implemented yet). That could involve doing some last-moment networking or other I/O, or really anything (we use it to restore servos to a rest position).
* technically, the body of a nursery itself is one of the tasks in the nursery, and it gets scheduled for cancel. This would be a chicken-and-egg problem, because it's valid to still want to run some code after calling cancel().
The example you gave is actually a little ambiguous, as it's not clear if that code is within the body of the nursery being cancelled, one if its child tasks, or neither (we haven't discussed the "neither" case yet, which turns out to be very useful). Assuming that it's within the nursery body or a child, then:
I.e. if the task you are in has been scheduled for cancel, the next attempt to yield control will raise Cancelled (again, unless you opt-out, which is a future topic). Though actually, the implementation has a bug here for the nursery body case-- I'll try to fix that soon.