Broken inheritActorContext?

I rolled my own global actor and a tiny view model with a method where I check the actor’s isolation.

@globalActor
actor AnotherGlobalActor {
  static var shared: some Actor = AnotherGlobalActor()
}

@Observable
final class DataSource: Sendable {

  func checkIsolationWithAnotherActor() async  {
	  AnotherGlobalActor.shared.assertIsolated("Assertation failed in runtime")
  }
}

When calling it from a Task, I hit a runtime error.

struct JustIsolated: View {
  @State
  private var data = DataSource()

  var body: some View {
	  Text("Task @_inheritActorContext or not?")
		  .onAppear {
			  Task { @AnotherGlobalActor in // 💥 error
				  await data.checkIsolationWithAnotherActor()
			  }
		  }
  }
}

In source of Task.swift we can see underscored attribute @_inheritActorContext:

  public init(
    priority: TaskPriority? = nil,
    @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async -> Success
  ) {
    fatalError("Unavailable in task-to-thread concurrency model.")
  }

Why doesn’t isolation work inside the body of a Task?

If I apply @AnotherGlobalActor an isolation on the method like this:

@AnotherGlobalActor
func checkIsolationWithAnotherActor() async  {
	AnotherGlobalActor.shared.assertIsolated("Assertation failed in runtime")
}

there’s no runtime errors. Is this expected behavior or am I missing something?

async functions don't just run on whatever thread they were called on, so while the closure you're passing to Task.init will run isolated to your global actor, the code in checkIsolationWithAnotherActor won't — it's non-isolated, so it switches off of actors.

It is generally wrong to call assumeIsolated or assertIsolated from an async function for this reason.

Can you provide more information on why such a check is incorrect?
Do you mean that the check should only be in a synchronous function?

Because async functions in general will be running on whatever specific isolation (actor) they're declared on, there's really rarely a reason to call assumeIsolated -- you already statically know where you're executing.

There's some rare cases where assumeIsolated may be useful in an async function, like multiple actors sharing the same executor etc, so it is not banned from being used in async functions, but it is highly unlikely you should be doing it.

It indeed mostly makes sense in synchronous functions which happen to be called from the isolation you're trying to recover.

1 Like

Many thanks :handshake: