Major +1, always a fan of proposals that enable greater expressiveness at compile-time. I have a request for a tweak in the semantics of this proposal, but first a motivating use case:
Motivation
I've found Concurrency to be massively useful in expressing API contracts for my NodeJS-Swift bridge:
Specifically, the above bridge is built on top of Node-API. Node allows you to have multiple "NodeJS threads" running in a process, each corresponding to a single instance of the Node interpreter. Any Node-API call must occur on the appropriate Node thread. I'm currently expressing this with a @globalActor actor NodeActor which has a custom executor that dispatches to the right Node thread. This allows for APIs along the lines of
final class NodeNumber: Sendable {
...
@NodeActor func doubleValue() -> Double
}
Unfortunately this doesn't have quite the right semantics, since
- It's non-trivial to determine which NodeJS thread the executor needs to dispatch a call to. Currently we're using a heuristic for this, namely a task-local that stores the "active" Node thread. But this is unreliable and is a source of Spooky Action at a Distance.
- A "global" NodeActor implies that all calls are serialized, even those occurring on different Node interpreter threads.
This proposal would pave the way to alleviate both of these problems. I would love to drop the @globalActor annotation on NodeActor and instead declare APIs like
final class NodeNumber: Sendable {
...
func doubleValue(in context: NodeActor = #isolation)
}
This would solve both of the aforementioned issues, while improving performance at the same time due to 1) being able to run jobs on different NodeActors in parallel and 2) not having to reach into a global/TaskLocal to determine the isolation context.
Suggestion
From what I can tell, the #isolation parameter can only be used with the static type (any Actor)? — even though the macro is able to resolve to a more specific type per this test.
It would be ideal if it were possible to narrow the #isolation parameter to a more specific type, such that if the user weren't in the expected context it would simply lead to a substitution failure. This would be great for the above use case, and IMO really any model that involves a thread pool. That request aside, I'm excited to see this proposal land!
5 Likes