TaskLocal memory usage

If you declare a task local does that imply a memory cost for every Task or just for the Tasks that use that task local?

Task locals only use memory if the task local is actually bound. Task locals are a linked list spanning across the structured task tree. So child task reference the tail element of the list from their parent task. This means task locals only consume memory when binding a task local.

There are two places where task locals are copied:

  1. Task.init copies the task locals from the surrounding context
  2. With Swift 6 the various task group addTask methods can potentially copy if a task local is bound around the addTask method.

An example of the second behaviour:

withTaskGroup(of: Void.self) { group in
  $myGreatTaskLocal.withValue(foo) {
    group.addTask {} // myGreatTaskLocal is copied here
    group.addTask {} // and here
  }
3 Likes

Is it a typo to put the withValue inside withTaskGroup? Or does that actually work now in Swift 6? Because currently you cannot override a task local inside withTaskGroup:

enum MyLocal {
  @TaskLocal static var value = 0
}
await withTaskGroup(of: Void.self) { group in
  MyLocal.$value.withValue(1) {}  // 💣 Runtime crash
}
1 Like

This is not a typo but works now. The runtime crash was removed in [Concurrency] Defensive copying of task locals in task groups, rather than crashing by ktoso · Pull Request #73978 · swiftlang/swift · GitHub by introducing a defensive copy of the task locals. This makes task locals fully usable in structured concurrency with the potential trade off in copying the values when a task local is scoped around a child task.

Importantly, this was a runtime crash so older runtimes will continue to crash but newer runtimes will copy when needed.

5 Likes

That's great to hear! It's still crashing for me in Xcode 16 beta 1, but hopefully soon I can give it a spin.

Is there a process for updating past proposals since SE-0311 specifically calls this out as not being allowed?