suppose you have two functions like this:
func inheritIsoParam(
iso: isolated (any Actor)? = #isolation
) async {
print("param iso")
}
nonisolated(nonsending)
func inheritIsoNINS() async {
print("nins")
}
and that you have a custom executor that logs when jobs are enqueued:
final class Exec: TaskExecutor, SerialExecutor {
let q = DispatchQueue(label: "q")
func enqueue(_ job: consuming ExecutorJob) {
print("ENQUEUE")
let j = UnownedJob(job)
q.async {
j.runSynchronously(
isolatedTo: self.asUnownedSerialExecutor(),
taskExecutor: self.asUnownedTaskExecutor()
)
}
}
}
when running in a task with this custom executor set as the task executor preference, i thought that calls to both of these functions would not result in an enqueue() call to the executor. i was under the impression that both functions inherit their callers executor so should not suspend upon entry. however, it appears i was mistaken, at least in some cases, such as this one:
@concurrent
func test() async {
let exec = Exec()
print("1")
await withTaskExecutorPreference(exec) {
print("2")
await inheritIsoParam()
print("3")
await inheritIsoNINS()
print("4")
}
print("5")
}
this prints (annotations added):
1
ENQUEUE
2
ENQUEUE <~~~ why??
param isolated
ENQUEUE
3
nins
4
ENQUEUE
5
as you can see, there is an enqueue that occurs when calling the parameter-isolated function, despite there being no change in isolation. this does not occur when calling the nonisolated(nonsending) variant though, which surprised me.
i noticed that this behavior only appears to occur if the custom executor passes a serial executor when running its jobs. e.g. if the jobs are instead run with runSynchronously(on: self.asUnownedTaskExecutor()) then it prints out:
1
ENQUEUE
2
param isolated
3
nins
4
5
and there's only a single enqueue onto the executor.
why is the behavior so different when a serial executor is involved? and specifically in this case, why does there appear to be a dynamic suspension of the task when calling the parameter-isolated function? is this behavior correct & expected?
godbolt: Compiler Explorer
related issue: withContinuation APIs appear to re-enqueue a job when calling the closure · Issue #85668 · swiftlang/swift · GitHub