Created task unable to inherit actor isolation

I have an use-case for actor which matches following snippet:

actor SomeActor {
    let executor = DispatchSerialQueue(label: "serial")
    var counter = 0
    nonisolated var unownedExecutor: UnownedSerialExecutor { executor.asUnownedSerialExecutor() }
}

func anyActorIsolatedFunc(_ act: isolated (any Actor)? = #isolation) {
    Task {
        if let act = act as? SomeActor {
            dispatchPrecondition(condition: .onQueue(act.executor)) // success
        }
        print("any actor start executed")
    }

    if let act = act as? SomeActor {
        dispatchPrecondition(condition: .onQueue(act.executor))
        Task {
            dispatchPrecondition(condition: .onQueue(act.executor)) // error
            print("any actor executed")
        }
    }

    Task {
        if let act = act as? SomeActor {
            dispatchPrecondition(condition: .onQueue(act.executor)) // success
        }
        print("any actor end executed")
    }
}

When trying to use anyActorIsolatedFunc:

await anyActorIsolatedFunc(act)
try await Task.sleep(nanoseconds: 1_000_000_000)

I am getting error in the Task created from the if block. From my understanding, the default task initializer always inherits current actor context and starts executing on the current actor’s executor. Is this a bug?

Assuming it's still up to date, SE-0420 mentions the rules for actor inheritance in Task initializers, saying (I'm paraphrasing a bit here) that the task inherits the isolation from the current context if either:

  • The current context is non-isolated.
  • The current context is isolated to a global actor.
  • A non-optional binding of an isolated parameter is captured by the closure.

Currently your code doesn't fall in any of the scenarios above. Closest one would be the last, as you do have an isolated parameter in the context. However, it's not captured by the closure. That may be all you need to do.


The behavior outlined in SE-0420 lines up with the documented behavior of @_inheritActorContext, which Task.init uses to implement its isolation inheritance semantics. Notice how the docs explicitly point out that while the attribute works for global actors as-is, for instance actors (isolated parameters) you need to capture them in the closure:

func test(actor: isolated (any Actor)) {
  Task { // @_inheritActorContext
    _ = actor // 'actor'-isolated 
  }
}
1 Like