`#isolation` not providing the correct Actor in closures when NonisolatedNonsendingByDefault is enabled?

In the following example, with NonisolatedNonsendingByDefault enabled, the closure passed to a.test() should be executed on actor a. The code does pass the a.assertIsolated() assertion successfully. However, when I try to get the current actor with the #isolation macro, I get a nil.

This also cause functions that use isolated parameter (e.g.: withTaskExecutorPreference) to fail to inherit the actor of their caller.

So what is happening here?

2 Likes

it seems likely that this is a bug. there was this patch that addressed some issues with the macro not working as intended in functions, but it seems it maybe doesn't yet handle closures?

@hborla @John_McCall or @ktoso, could one of you confirm whether this is a bug please? should it ever be the case that an async function/closure can both have an actor isolation assertion pass and the #isolation macro expansion resolve to nil (i assume not)?


as a workaround, it seems you could possibly use a local function instead of a closure:

@concurrent
nonisolated func test() async {
    let a = MyActor()

    nonisolated(nonsending)
    func f() async -> Void {
        a.assertIsolated()  // OK
        let iso = #isolation
        print(iso as Any)   // Optional(MyActor)
    }

    await a.test(f)
}

Yes, it looks like Konrad's patch just doesn't properly handle isolated code that doesn't correspond to a function declaration. I'm putting together a fix.

4 Likes

filed an issue: #isolation macro unexpectedly `nil` in `nonisolated(nonsending)` closures · Issue #84171 · swiftlang/swift · GitHub

2 Likes