I'm curious and playing around with various Global Actor mechanisms, and I ran into this error message that doesn't make sense to me. Here's the code for it:
@globalActor
actor MyGlobalActor : GlobalActor {
var timeStamp = Date.now
// I want a non-isolated function to pop out an actor isolated task to
// change a property in my actor
nonisolated func update() {
Task { @MyGlobalActor in
timeStamp = .now
}
}
static let shared = MyGlobalActor()
}
It makes sense to me to get this message if I have just the nonisolated declaration modifier in place, but I think I have also marked the Task's closure to run on the right actor with @MyGlobalActor in. Can someone tell me what I'm missing, please?
The isolation checker currently does not understand the equivalence of the @SomeGlobalActor and the instance, they're seen as separate isolations and therefore the issue.
The solution to your problem here specifically would be provided by Closure isolation control but that's not been implemented for 6.0, but it's still something we're interested in doing one way or another.
Alternatively, there were proposals flying around about improving the isolation checker to understand the equivalence of an instance of a global actor to the global actor itself... (under the assumptiuon that there is just ONE such instance; you could violate this promise though). So then your code would have worked as is.
Either solutions don't exist though.
Today what I'd do is: move the state out of this actor instance entirely -- it's only going to be used as the thread serialization mechanism, and utilize the fact that you have a global actor to put state on:
@MyGlobalActor
var timeStamp = Date.now
@globalActor
actor MyGlobalActor : GlobalActor {
nonisolated func update() {
Task { @MyGlobalActor in
timeStamp = .now
}
}
static let shared = MyGlobalActor()
}
I have a follow-up question: what if I also needed a synchronous function that I might use within my actor, modifiying that global state? For instance, if I wanted to create a function, peer to the update function, as follows:
private func updateNow() {
timeStamp = .now
}
This gives the error, "Global actor 'MyGlobalActor'-isolated var 'timeStamp' can not be mutated on a different actor instance". It sounds like this is caused by the same equivalence-blindness in the isolation checker. Do you have a further workaround for this situation?
Or is this redundant? Is it the case that running the original update from within the Actor would end up running the contained Task immediately? I seem to recall that calling await MainActor.run from a MainActor async context will run its given closure immediately, in a synchronous fashion.
Yeah, wanted to confirm we're talking about the same code; Things get confusing without specific code to talk about with the checking rules as there's various things impacting it
Yeah so that's the same story really but in the inverse. Previously you had a @ MyGlobalActor isolated task trying to mutate an instance; and now the updateNow() is in the instance but trying to mutate a @MyGlobalActor property.
It's the other side of the same coin, where we don't see the equivalence of those two actors.
Swift is "right" here, because you could:
let other = MyGlobalActor()
let another = MyGlobalActor()
Task { await other.updateNow() }
Task { await another.updateNow() }
So you see how you have two instances... but you'd try to mutate the same single global one -- so Swift is right in that this isn't just safe to assume like that.
--
The future work I'm alluding to would allow this given the promise that you'd never create more instances than the specific let shared instance; then we could have the compiler trust that "ok yeah, .shared instance is the same as the @GlobalActor.
The only thing I still wonder is whether it would be a good idea to have the GlobalActor protocol require only private initializers. That might prevent client code from breaking the singleton assumption.
Yeah though thatβd be source breaking nowβ¦ weβll have to figure something out, maybe at least warning about suspicious other instance creations hmmm