SE-0504: Task Cancellation Shields

I continue to think that this is an important enough point—the distinction between "the effect of being canceled" and "being canceled"—that consistent feedback wondering why it's not called "ignored" sends a signal that using "shield" as a term of art is not a good enough solution.

I think in part it is because the analogy is faulty: as we discussed during the pitch phase, if you shoot an arrow at a shield, the arrow doesn't hover in midair waiting to hit the target once the shield is dropped. By the "shield" analogy, an arrow blocked by a shield is ignored, so substituting one term for the other doesn't solve the problem.

This is a niche enough feature with a significant enough nuance that—based on this empiric evidence of ongoing, continued reader confusion—I would urge continued exploration as to what terminology could make the model clearer. Other terms that come into mind which do convey the sense that something is temporarily held back rather than overridden:

  • withTaskCancellationDeferred — I think this conveys the right idea by analogy with defer that it's a "clean-up" feature that wraps things up; in this sense and by analogy, it head-on contradicts the misunderstanding that something is "ignored" forever
  • withTaskCancellationSuspended
  • withTaskCancellationInAbeyance — (now we're lawyers)

I continue to think that, even only on UnsafeCurrentTask, we should not have this spelled out as an API. I guess some of this is contingent on whether the same result can already be achieved by querying task.isCancelled == Task.isCancelled, based on the next section.

Regardless, though, this situation rhymes with prior instances where we've said that adding some API but then obfuscating it with namespaces or in wordier types is not the way: either it's good API or it's not. Namespacing it under UnsafeCurrentTask might be justifiable if the API is literally concurrency-unsafe, but afaict it's not (at least not per se)—the motivation given, at least, is to just hide it or make it seem scarier so it's less used.


I think this is the correct behavior. However, the proposal is ambiguous on a salient point. It says only that the instance method task.isCancelled queried from outside of the task will return the actual cancelled state regardless of cancellation shield. The rationale given is that it is racy to consider any other behavior.

However, what is the proposed behavior if the instance method is queried from inside of the task? It would not be racy: by that logic, it would seem that it should respect cancellation shields: in other words, Task.isCancelled == task.isCancelled always if task is the current task. However, there is also a case to be made that we should have task.isCancelled always behave as-though it is not in a cancellation shield scope. This would make the API exhibit the same behavior in every context—much simpler to understand.

(It would also incidentally make Task.isCancelled == task.isCancelled a way to query if we're under a shield for debugging purposes, without vending a dedicated API that we have to then warn people not to use in production—see previous section.)

Either way, this proposal should spell out explicitly what the behavior is of task.isCancelled when it is invoked in the context where task is the current task.

10 Likes