[Pitch] Inheriting the caller's actor isolation

Hi. One problem we've been seeing with Swift concurrency is that there isn't an easy way for async functions to polymorphically share their callers' actor isolation. This makes it hard to use certain kinds of async abstractions from isolated contexts because technically the async functions in the abstraction have a different isolation, which means e.g. you can't share non-Sendable values with them. It also can have unwanted performance consequences.

I've written a proposal which adds a handful of related features that should go a long way towards solving this problem. This hasn't been implemented yet, so it might change substantially, but I'd be interested in hearing what folks think about it.

Please make editorial comments on the pull request; everything else can go in this thread.

5 Likes

It brings to mind a reisolated attribute in the same vein as rethrows, to indicate that a function (and by extension its return value(s)) are isolated in the same way as one or more of its arguments (in which case all arguments must agree on their isolation domain).

Re. the "isolation" parameter, it seems a bit weird to have a function parameter that's not actually referenced in the function body. It seems suboptimal in the same way as it would be if you had to always write "self: Self" as the first parameter for instance methods.

My intuition in that regard is to expand the existing isolated function prefix, complimentary to the existing nonisolated. Currently you can't actually write isolated func(…) explicitly (I believe), it's only accessible by implication when writing actor instance methods. Making the keyword usable explicitly would allow it to expand to all functions, perhaps with suitable arguments (e.g. isolated(caller)).

1 Like

Hi there! This seems like it would solve some pain points that I've run across, so I'm interested to see this implemented.

Just to check my understanding: is there any reason for someone to use isolated (any Actor)? = #isolation instead of @inheritsIsolation besides allowing callers to (optionally) explicitly provide an isolation value that's different from the current isolation?

Note that this proposal is not introducing isolated parameters, which were added in SE-0313.

It also allows the value to be used explicitly within the function.

1 Like

This appears to cover all of the remaining problems I ran into while implementing sendability and actor isolation in Realm.

We have a few functions which take a mutable, NSCopyable obj-c object as an argument, which we want to deep copy and stuff in an @unchecked Sendable wrapper before unblocking the caller. Currently we use @_unsafeInheritExecutor for this. This has been error-prone due to that it doesn't actually inherit the isolation and so doesn't resume on the same executor after an await. We've been able to dodge the problem so far, but it's also easy to imagine wanting to unwrap a non-sendable type in the caller's isolation, which isn't possible with @_unsafeInheritExecutor. @inheritsIsolation appears to fix these problems, and of course gets us off an underscored attribute.

#isolation is also something we've wanted in a few spots. The simplest example is that we'd like try await Realm() to give the caller an object isolated to the caller's isolation context. Currently we require that users do try await Realm(actor: actor) and explicitly pass in the current actor. In addition to being clunky and confusing, this has the significant problem that passing in the wrong actor is a runtime error, and Swift 5.9 has started producing sendability warnings even if the correct actor is passed in.

I'm not entirely confident about this, but I suspect that all of the places where we'd use #isolated could also be achieved via a combination of @inheritsIsolation and @_inheritActorContext. You can't really do anything with an any Actor other than use it as an isolated parameter later, and we could replace storing the actor with storing an appropriately isolated closure. There isn't any reason we'd want to do this if exactly this proposal was implemented, but if for some reason #isolated turns out to be a problem, then expanding inheritsIsolation to also subsume @_inheritActorContext may also be a viable approach.

4 Likes