[Accepted] SE-0504: Task Cancellation Shields

Hello, Swift community.

The review of SE-0504: Task Cancellation Shields initially ran from January 12th through January 26th, 2026. There was quite a lot of discussion in the review thread, mostly focused on the naming concept of a "shield". The Language Steering Group then informally extended the review for a few weeks to ask for additional feedback about the proposed hasActiveTaskCancellationShield API.

Overall, the LSG is satisfied with the name "shield". First, we believe the basic semantics laid out in the proposal — cancellations of the task are not normally observable while the shield is active and do not propagate to handlers and subtasks created within the shield — are the right ones for this operation. Since cancellation does still actually happen immediately and simply isn't observed by nested handlers and subtasks, this is a pretty subtle rule, so we were dissatisfied with most of the counter-proposals that talking about "delaying" or "suppressing" cancellation. We feel that the shield metaphor is somewhat intuitive, but in case it is not, it is better that programmers look it up in the documentation than be misled by an imprecisely evocative name.

The LSG was initially concerned about making hasActiveTaskCancellationShield too obvious in the API because of the potential for misuse. However, we are persuaded that it is important to provide in some way, both for debugging and for eliminating unnecessary setup work with cancellation handlers when cancellation is impossible. The potential for misuse is therefore not entirely avoidable. Given that, the best way to reduce it is to clearly address it in the documentation for hasActiveTaskCancellationShield by explaining that code should never have observably different behavior in the presence of a shield. There is then no reason to demote this API to the unsafe interface of UnsafeCurrentTask, and its use in manual optimization — even if somewhat uncommon in practice — justifies it being part of the static API of Task alongside isCancelled.

While we think a general task-status debugging API would be valuable, we do not want to hold up this proposal on it.

SE-0504 is therefore accepted with the revision of moving hasActiveTaskCancellationShield to be a static property of Task.

I'd like to thank the community for its attention and contributions in this review.

John McCall
Review Manager

12 Likes

My apologies. I've just remembered that technically this is an acceptance with revisions, because the proposal that was reviewed did not include the static property Task.hasActiveTaskCancellationShield; it only had this as an instance property of UnsafeCurrentTask. This was a static property on Task earlier in the pitch. I have revised the announcement above to describe this correctly. I believe the text above now adequately explains why the LSG felt it was appropriate to restore this API to Task.

John McCall
Review Manager

4 Likes

To this day, I still don't understand what the "active" part of hasActiveTaskCancellationShield is supposed to mean, and certainly wasn't the only one tripping up on its meaning, so I'm surprised to see it unchanged and promoted under Task, of all things.

3 Likes

Not sure why you're linking to my review, as "active" is pretty much the only part of that API which I didn't give feedback on :)

Anyway, I'm not sure how one would solve this concern, because the decision is that the API uses the metaphor of a "shield" for this bit of state. When this bit is "on," you might say that your shield is active or deployed or raised or used; but even when this bit is "off," you still "have" the shield. Unlike cake, where you can't have it and eat it too, you must have a shield to use a shield (or not). Thus, hasTaskCancellationShield wouldn't be an appropriate name, nor was any other suitable alternative put forward during the review.

I suppose one could think about isTaskCancellationShielded, or isShieldedFromTaskCancellation (in the latter case particularly, Task.isShieldedFromTaskCancellation would be redundantly named and could just be Task.isShieldedFromCancellation).

Maybe I misunderstood the exchange, but towards the end of your post you mentioned having Task.isCancelled == task.isCancelled as "a way to query if we're under a shield":

(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.)

But this is distinct from the behavior of hasActiveTaskCancellationShield, as the latter is true even when the shield is not changing the value of Task.isCancelled.

That was pointed out in the replies, where this example came up:

Task {
  withTaskCancellationShield {
    withUnsafeCurrentTask {
      print($0!.hasActiveTaskCancellationShield)
    }
  }
}

And it was then confirmed that the shield is "active" (hasActiveTaskCancellationShield returns true) regardless of whether the current task is cancelled or not.

This exchange is what led me to believe —perhaps mistakingly— that there had been different interpretations of the meaning of hasActiveTaskCancellationShield in the thread. If that was not the case, well, that'd explain a lot!

But then I'm not quite getting the point you're making here:

If the behavior of hasActiveTaskCancellationShield is that it's unconditionally true when queried from within the scope of a withTaskCancellationShield { ... }, how can you "have" the shield but not have it "active"?

Unless you're saying that even outside of a withTaskCancellationShield { ... } scope you somehow also "have" a shield, because a bit exists in the internal representation of a task to set one? But the API is literally called with[...]Shield { ... }, how could anyone conceivably interpret that you "have" a shield outside that scope too?

1 Like

If you’re just saying that you don’t think “active” is necessary because a shield is always active, I think that’s a reasonable point. I don’t anticipate the LSG changing the name now, though.

I followed the review thread moderately closely, though I didn’t pay too close attention to the discussion about hasActiveTaskCancellationShield, and I came away with the loose impression that indeed it would only be true if it was actively suppressing observation of a cancellation, mainly because the name sounded like that to me rather than because of anything I read in the thread

It is true that the here is no “have a shield but it is not active” either it is active, or not. When I proposed those names I felt this was more clear. The review didn’t really focus on this API much so it stuck. I don’t think it’s a bad name, and I’ve used the “have a shield active” in documentation of the feature, so I think it reads well enough.

The proposal, as accepted, does not introduce an instance method, only a static method Task.hasTaskCancellationShield.

The request to not include the instance method came from the fact that querying it would be inherently racy and could lead to more confusion than it was trying to solve.

1 Like

I'll try not to read too much into that typo :stuck_out_tongue:

Not sure why you're bringing up instance-on-UnsafeCurrentTask vs static-on-Task, though. In the review thread the code snippets were using the instance method, but I presume the semantics are the same in the (accepted) static version:

Task {
  withTaskCancellationShield {
      print(Task.hasActiveTaskCancellationShield) // <- Always true
  }
}

Anyway, what's done is done. I was just somewhat surprised by the outcome.

I was clarifying that this snippet may be misleading as we don’t offer an instance property on Task. You can do unsafe sneaky things with UnsafeCurrentTask but we ended up not offering this api on Task as an instance property.