Synchronization Of Non-isolated Property In Actor

I hope this message finds you well.

I am currently working with some legacy code that presents significant challenges in terms of refactoring. As a result, I am unable to implement asynchronous getters in certain actors. Consequently, I opted to make one of the actor's properties non-isolated, which has led to the emergence of a data race.

actor SomeActor {

// laziness is used to silence a warning
// Original warning "'nonisolated' can not be applied to stored properties"

nonisolated private(set) lazy var num: Int = 0

func updateNum() {
    num += 1
  }
}

NSLocks do not seem to have an impact here.
I would appreciate any insights or recommendations you might have regarding this issue.

The fact that nonisolated lazy allowed on an actor is a bug: https://forums.swift.org/t/why-nonisolated-lazy-var-is-allowed-on-actor.

You can opt-out from using actor and implement synchorization using NSLock within class that you will mark as @unchecked Sendable (so that you have to ensure this on your own), if the goal is to have synchronous access.

2 Likes

Could you please explain to me why locks do not work in an asynchronous context? Furthermore, it is an error in Swift 6

final class SomeClass {
    private(set) var num = 0
    private let lock = NSLock()

func foo() async {
    lock.lock()
    num += 1
    lock.unlock()
  }
}

When you use an NSLock, the runtime knows what thread you used it from, so when a higher priority thread wants to use the same lock, it can bump up the priority of the original thread to get it unlocked faster. When you are in an async context, the runtime doesn't know which thread to boost. You can use withLock, which has a non-async block as an input, to avoid this error.

1 Like

There is a great explanation in the evolution proposal if you want to read in detail: swift-evolution/proposals/0340-swift-noasync.md at main · swiftlang/swift-evolution · GitHub

But the shortened version, is that because async functions can abandon thread, and locks essentially has to be locked and unlocked from the same thread. Given that async function can resume on a different thread, using separate lock/unlock is unsafe. You can however use withLock as locking around code that has no suspension points is clearly safe.

2 Likes