Swift, actor: Actor-isolated property 'scanning' can not be mutated from a non-isolated context - swift

I have an actor:
actor StatesActor {
var job1sActive:Bool = false
...
}
I have an object that uses that actor:
class MyObj {
let myStates = StatesActor()
func job1() async {
myStates.job1IsActive = true
}
}
Line:
myStates.job1IsActive = true
errors out with this error:
Actor-isolated property 'job1IsActive' can not be mutated from a non-isolated context
How can I use an actor to store/read state information correctly so the MyObj can use it to read and set state?

How can I use an actor to store/read state information correctly so the MyObj can use it to read and set state?
You cannot mutate an actor's instance variables from outside the actor. That is the whole point of actors!
Instead, give the actor a method that sets its own instance variable. You will then be able to call that method (with await).

It is not allowed to use property setters from code that is not run on the actor ("actor isolated code" is the exact terminology). The best place to mutate the state of an actor is from code inside the actor itself. In your case:
actor StatesActor {
var job1IsActive: Bool = false
func startJob1() {
job1IsActive = true
...
}
}
class MyObj {
let myStates = StatesActor()
func job1() async {
await myStates.startJob1()
}
}
Async property setters are in theory possible, but are unsafe. Which is probably why Swift doesn't have them. It would make it too easy to write something like if await actor.a == nil { await actor.a = "Something" }, where actor.a can change between calls to the getter and setter.
The answer above works 99% of the time (and is enough for the question that was asked). However, there are cases when the state of an actor needs to be mutated from code outside the actor. For the MainActor, use MainActor.run(body:). For global actors, #NameOfActor can be applied to a function (and can be used to define a run function similar to the MainActor). In other cases, the isolated keyword can be used in front of an actor parameter of a function:
func job1() async {
await myStates.startJob1()
let update: (isolated StatesActor) -> Void = { states in
states.job1IsActive = true
}
await update(myStates)
}

Related

Cannot safely access globalActor's method from a closure marked with same globalActor

#globalActor
actor LibraryAccount {
static let shared = LibraryAccount()
var booksOnLoan: [Book] = [Book()]
func getBook() -> Book {
return booksOnLoan[0]
}
}
class Book {
var title: String = "ABC"
}
func test() async {
let handle = Task { #LibraryAccount in
let b = await LibraryAccount.shared.getBook() // WARNING and even ERROR without await (although function is not async)
print(b.title)
}
}
This code generates the following warning:
Non-sendable type 'Book' returned by call to actor-isolated instance method 'getBook()' cannot cross actor boundary
However, the closure is itself marked with the same global Actor, there should be no Actor boundary here. Interestingly, when removing the await from the offending line, it will emit an error:
Expression is 'async' but is not marked with 'await'
This looks like it does not recognize that this is the same instance of the Actor as the one guarding the closure.
What's going on here? Am I misunderstanding how GlobalActors work or is this a bug?
Global actors aren't really designed to be used this way.
The type on which you mark #globalActor, is just a marker type, providing a shared property which returns the actual actor instance doing the synchronisation.
As the proposal puts it:
A global actor type can be a struct, enum, actor, or final class. It is essentially just a marker type that provides access to the actual shared actor instance via shared. The shared instance is a globally-unique actor instance that becomes synonymous with the global actor type, and will be used for synchronizing access to any code or data that is annotated with the global actor.
Therefore, what you write after static let shared = doesn't necessarily have to be LibraryAccount(), it can be any actor in the world. The type marked as #globalActor doesn't even need to be an actor itself.
So from Swift's perspective, it's not at all obvious that the LibraryAccount global actor is the same actor as any actor-typed expression you write in code, like LibraryAccount.shared. You might have implemented shared so that it returns a different instance of LibraryAccount the second time you call it, who knows? Static analysis only goes so far.
What Swift does know, is that two things marked #LibraryAccount are isolated to the same actor - i.e. this is purely nominal. After all, the original motivation for global actors was to easily mark things that need to be run on the main thread with #MainActor. Quote (emphasis mine):
The primary motivation for global actors is the main actor, and the
semantics of this feature are tuned to the needs of main-thread
execution. We know abstractly that there are other similar use cases,
but it's possible that global actors aren't the right match for those
use cases.
You are supposed to create a "marker" type, with almost nothing in it - that is the global actor. And isolate your business logic to the global actor by marking it.
In your case, you can rename LibraryAccount to LibraryAccountActor, then move your properties and methods to a LibraryAccount class, marked with #LibraryAccountActor:
#globalActor
actor LibraryAccountActor {
static let shared = LibraryAccountActor()
}
#LibraryAccountActor
class LibraryAccount {
static let shared = LibraryAccount()
var booksOnLoan: [Book] = [Book()]
func getBook() async -> Book { // this doesn't need to be async, does it?
return booksOnLoan[0]
}
}
class Book {
var title: String = "ABC"
}
func test() async {
let handle = Task { #LibraryAccountActor in
let b = await LibraryAccount.shared.getBook()
print(b.title)
}
}

What is the Swift concurrency equivalent to a promise–resolver pair?

With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).

What is the `nonisolated` keyword in Swift?

I recently came across the nonisolated keyword in Swift in the following code:
actor BankAccount {
init(balance: Double) {
self.balance = balance
}
private var balance: Double
nonisolated func availableCountries() -> [String] {
return ["US", "CA", "NL"]
}
func increaseBalance(by amount: Double) {
self.balance += amount
}
}
What does it do? The code compiles both with and without it.
The nonisolated keyword was introduced in SE-313. It is used to indicate to the compiler that the code inside the method is not accessing (either reading or writing) any of the mutable state inside the actor. This in turn, enables us to call the method from non-async contexts.
The availableCountries() method can be made nonisolated because it doesn't depend on any mutable state inside the actor (for example, it doesn't depend on the balance variable).
When you are accessing different methods or variables of an actor, from outside the actor, you are only able to do so from inside an asynchronous context.
Let's remove the nonisolated keyword from the availableCountries() method. Now, if we want to use the method, we are only allowed to do so in an async context. For example, inside a Task:
let bankAccount = BankAccount(balance: 1000)
override func viewDidLoad() {
super.viewDidLoad()
Task {
let availableCountries = await bankAccount.availableCountries()
print(availableCountries)
}
}
If we try to use the method outside of an async context, the compiler gives us errors:
However, the availableCountries() isn't using any mutable state from the actor. Thus, we can make it nonisolated, and only then we can call it from non async contexts (and the compiler won't nag).

Accessing actor properties synchronously from task-less context

Integrating actors with existing code doesn't really seem to be as simple as Apple wants you to believe. Consider the following simple actor:
actor Foo {
var value: Int = 0
}
Trying to access this property from any AppKit/UIKit (task-less) controller just can't work because every Task is asynchronous.
class AppKitController {
func myTaskLessFunc() {
let f = Foo()
var v: Int = -1
Task { v = await f.value }
}
}
This will give you the expected error Mutation of captured var 'v' in concurrently-executing code, also if you tried to lock this it wouldn't work. nonisolated actor methods also don't help, since you will run into the same problem.
So how do you read actor properties synchronous in a task-less context at all?
I found a way to do it by using Combine, like this:
import Combine
actor Foo {
nonisolated let valuePublisher = CurrentValueSubject<Int, Never>(0)
var value: Int = 0 {
didSet {
valuePublisher.value = value
}
}
}
By providing an non-isolated publisher, we can propagate the actor value safely to the outside, since Publishers are thread safe.
Callers can either access foo.valuePublisher.value or subscribe to it from the outside, without requiring an async context.

Actor ‘self’ can only be passed ‘inout’ from an async initializer

In Swift, let’s say we have an actor, which has a private struct.
We want this actor to be reactive, and to give access to a publisher that publishes a specific field of the private struct.
The following code seems to work. But it produces a warning I do not understand.
public actor MyActor {
private struct MyStruct {
var publicField: String
}
#Published
private var myStruct: MyStruct?
/* We force a non isolated so the property is still accessible from other contexts w/o await in other modules. */
public nonisolated let publicFieldPublisher: AnyPublisher<String?, Never>
init() {
self.publicFieldPublisher = _myStruct.projectedValue.map{ $0?.publicField }.eraseToAnyPublisher()
}
}
The warning:
Actor 'self' can only be passed 'inout' from an async initializer
What does that mean? Is it possible to get rid of this warning? Is it “dangerous” (can this cause issues later)?
Note:
If I use $myStruct instead of _myStruct.projectedValue, it does not compile at all. I think it’s related, but I don’t truly see how.
The error is in that case:
'self' used in property access '$myStruct' before all stored properties are initialized
The warning is because when you are using _myStruct.projectedValue compiler notes that you are trying to do mutation on the actor from a synchronous context. All you have to do to remove the warning is to make your initializer asynchronous using async specifier.
The actor initializer proposal goes into more detail for why initializer needs to be asynchronous if there mutations inside actor initializer. Cosider the following case:
actor Clicker {
var count: Int
func click() { self.count += 1 }
init(bad: Void) {
self.count = 0
// no actor hop happens, because non-async init.
Task { await self.click() }
self.click() // 💥 this mutation races with the task!
print(self.count) // 💥 Can print 1 or 2!
}
}
Since there are two click method call one from Task and one called directly, there might be a case that the Task call executed first and hence the next call need to wait until that finishes.