I'm attempting to move from global isolation to a more-general isolated parameter. I thought for sure this was going to work but it does not. I have a guess as to what is going on here, but I'm looking for confirmation.
class NonSendable {
}
class TestThing {
private let value = NonSendable()
func notOk(isolation: isolated (any Actor)? = #isolation) {
let op = {
print(self.value)
}
Task {
// ERROR: 'isolation'-isolated value of type '() async -> ()' passed as a strongly transferred parameter; later accesses could race
op()
}
}
@MainActor
func ok() {
let op = {
print(self.value)
}
Task {
op()
}
}
}
Capturing self in op is the key.
My guess is that in the ok example, self is implicitly Sendable because it is also implicitly MainActor-isolated. Or maybe op as a whole?
Either way, I don't fully understand why this is semantically different. How could later accesses race if the isolation both outside and inside of the Task in notOk is always the same?
Calling notOk from actor A and then again from actor B captures op (and, in turn, the non-sendable self.value) into tasks isolated to two different actors.
Ahh, of course! Without the changes from Closure isolation control, I have to be really careful about how isolation is inherited within that Task.
Here's the solution:
func notOk(isolation: isolated (any Actor)? = #isolation) {
let op = {
print(self.value)
}
Task {
_ = isolation // this is critical to get the correct inheritance
op()
}
}
Oh, my bad — I have been assuming for some reason that if an instance is protected by e.g. a global actor, like
@MainActor
static let testThing = TestThing()
this would make it so that other actors would still be able to reach out to it and call notOk each with their own isolation. Obviously, and luckily, this doesn't happen .
Well, I'm cycled back to this same thing in just a slightly different form. This version, again, looks to me like it should work. But, the isolation capture in this case doesn't fix the issue and I don't fully understand why.
The use of the function + closure seems to be the issue. But, since the closure is non-Sendable and not even escaping, I'm not sure why the global isolation would make a difference.