Task creation and actor inference in nonisolated synchronous functions

suppose we have a non-isolated, synchronous function in which we want to spawn a Task that will execute its work on no actor. an initial approach might look like this:

func spawnBgWorkSync() {
  Task { /* ... */ }
}

however, the Task's closure parameter has the @_inheritActorContext attribute, which raises the question: is the code in the example guaranteed to never run on an actor?

the actor inheritance proposal (SE-420) states:

Non-isolated synchronous functions dynamically inherit the isolation of their caller. For example, an actor method can call a non-isolated synchronous function, and the function will behave dynamically as if it is isolated to the actor.

but it is unclear to me what the implications of this are on the effective runtime isolation of the Task's operation parameter, if any.

per SE-338, we can currently ensure that async work occurs off-actor by using a non-isolated async function, or via Task.detached, so presumably either of the following would achieve the desired goals:

func spawnBgWorkSyncDetached() {
  Task.detached { /* ... */ }
}

func spawnBgWorkSync() {
  Task { await doTheWork() }
}

private nonisolated doTheWork() async {
  // ...
}

but in the non-detached Task case, there is still a lingering question about whether the Task could somehow inherit actor isolation, which would then necessitate a needless 'hop' to actually execute its effective work.

to rephrase the underlying questions/motivation here a bit –

if one wants to implement a synchronous, non-isolated function that:

  1. spawns an unstructured Task that runs on the global concurrent executor
  2. is resilient to external refactoring affecting its statically-inferred isolation
  3. is resilient to different caller-isolation affecting its runtime-inferred dynamic isolation
  4. minimizes needless 'hops' between actors

what are the best tools for the job?

1 Like

AFAIK, there are only two ways to provide isolation.

  1. Existing actor context. This is static and should be determinable at build time.
  2. Dynamic isolation values of some kind. These tools have expanded so I may not know all of them now, but they're always explicit.

So if there's no existing actor context and no dynamic context provided, there's no need to worry about things changing at runtime.

1 Like