# What does "Use async-safe scoped locking instead" even mean?

**URL:** <https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029>\
**Category:** Using Swift\
**Created:** [October 26, 2022, 9:38am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029 "2022-10-26T09:38:12Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 9:38am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/1 "2022-10-26T09:38:12Z")

</div>

In Xcode 14.1 there is a new warning for using `NSLock.lock()` inside an `async` function, that says

> Instance method 'lock' is unavailable from asynchronous contexts; Use async-safe scoped locking instead; this is an error in Swift 6

What does "async-safe scoped locking" even mean, since there are no locks in swift concurrency?

What I actually need is a method that guarantees that a certain function call can not run in parallel (@synchronized). The closest I can think of in Swift is the following, but I am not certain that this is not racy:

```swift
actor Foo {

    var isCalling = false

    func call(_ i: Int) async {
        while isCalling {
            await Task.yield()
        }
        isCalling = true
        defer {
            isCalling = false
        }
        print("---")
        print(i)
        await sleepRandom()
        print(i)
    }

    private func sleepRandom() async {
        try! await Task.sleep(nanoseconds: UInt64.random(in: 100_000_000...500_000_000))
    }
}

```

The problem with `actor` is, that `call()` could still be invoked while `await sleepRandom()` has suspended the previous task invoking `call()`.

How am I supposed to replace a `lock()` call in Swift concurrency?

---

<div class="post-metadata">

**Author:** ![lukasa](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukasa/32/1232_2.png) [@lukasa](https://forums.swift.org/u/lukasa)\
**Post date:** [October 26, 2022, 9:41am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/2 "2022-10-26T09:41:57Z")

</div>

It means to use `Lock.withLock`. This establishes a "scope" in which you are holding the lock (hence "scoped locking"), and it forbids `await` calls within that block, so it's guaranteed to be safe.

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 9:43am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/3 "2022-10-26T09:43:04Z")

</div>

Ok, so that seems new in macOS 13, which I can't use yet. My deployment target is still 12. Also it seems `Lock.withLock` won't work, since I have to do a network request (refresh an authentication token) which is async while its locked.

---

<div class="post-metadata">

**Author:** ![lukasa](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukasa/32/1232_2.png) [@lukasa](https://forums.swift.org/u/lukasa)\
**Post date:** [October 26, 2022, 9:44am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/4 "2022-10-26T09:44:20Z")

</div>

You can straightforwardly reinvent it:

```swift
extension NSLock {
    func withLock<ReturnValue>(_ body: () throws -> ReturnValue) rethrows -> ReturnValue {
        self.lock()
        defer {
            self.unlock()
        }
        return try body()
    } 
}

```

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 9:45am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/5 "2022-10-26T09:45:41Z")

</div>

> [@lukasa](#):
>
> and it forbids `await` calls within that block

This is a problem, I need to ensure that a network request is only done once (refreshing an auth token).

So `withLock` is not an option as it seems, even if I use a shim :)

---

<div class="post-metadata">

**Author:** ![lukasa](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukasa/32/1232_2.png) [@lukasa](https://forums.swift.org/u/lukasa)\
**Post date:** [October 26, 2022, 9:55am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/6 "2022-10-26T09:55:05Z")

</div>

Correct: you'll need a different object. You're conceptually looking for something like a token bucket or a queue. The downside of a token bucket is that it's inherently unowned, so you don't get sensible priority donation by waiters, but a better primitive isn't something you can build yourself so for now it's probably your best option.

A simple token bucket for the degenerate case of having only one token looks like this:

```swift
actor TokenBucket {
    enum State {
        case full
        case empty(waiters: [CheckedContinuation<Void, Never>])
    }

    private var state: State

    init() {
        self.state = .full
    }

    private func enter() async {
        switch self.state {
        case .full:
            self.state = .empty(waiters: [])
            return
        case .empty(waiters: var waiters):
            await withCheckedContinuation {
                waiters.append($0)
                self.state = .empty(waiters: waiters)
            }
        }
    }

    private func exit() {
        guard case .empty(waiters: var waiters) = self.state else {
            fatalError("Exiting in invalid state")
        }

        if waiters.isEmpty {
            self.state = .full
            return
        }

        let nextWaiter = waiters.removeFirst()
        self.state = .empty(waiters: waiters)
        nextWaiter.resume()
    }

    func withToken<ReturnValue>(_ body: () async throws -> ReturnValue) async rethrows -> ReturnValue {
        await self.enter()
        defer {
            self.exit()
        }
        return try await body()
    }
}

```

This is not really an ideal primitive for the reasons noted above, but it will work for the basic use-case.

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 10:09am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/7 "2022-10-26T10:09:52Z")

</div>

Thanks. This seems really excessive for basic functionality.

This is a worrysome trend (the last 10 years) that Apple always introduces new things that do not have at least the feature set of the old thing.

Just as an example, I have tried to use Actors in a meaningful context for a year now, but there are none (except for `@MainActor`).

---

<div class="post-metadata">

**Author:** ![dnadoba](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dnadoba/32/11438_2.png) [@dnadoba](https://forums.swift.org/u/dnadoba)\
**Post date:** [October 26, 2022, 10:11am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/8 "2022-10-26T10:11:57Z")

</div>

If I understood your problem correctly and you only want to do an async request once we can simplify this. We can just use a lazily initialised `Task` and cache it:

```swift
actor Cache {
    lazy var token: Task<String, Error> = Task {
        // do request
        return "my token"
    }
    func getToken() async throws -> String {
        return try await token.value
    }
}

```

The [WWDC talk about actors](https://developer.apple.com/videos/play/wwdc2021/10133/?time=713) uses a similar approach but a bit more complicated as it caches request for a given URL.

The complete solution is not shown in the video but attached to the session. Here it is:

```swift
actor ImageDownloader {

    private enum CacheEntry {
        case inProgress(Task<Image, Error>)
        case ready(Image)
    }

    private var cache: [URL: CacheEntry] = [:]

    func image(from url: URL) async throws -> Image? {
        if let cached = cache[url] {
            switch cached {
            case .ready(let image):
                return image
            case .inProgress(let task):
                return try await task.value
            }
        }

        let task = Task {
            try await downloadImage(from: url)
        }

        cache[url] = .inProgress(task)

        do {
            let image = try await task.value
            cache[url] = .ready(image)
            return image
        } catch {
            cache[url] = nil
            throw error
        }
    }
}

```

I'm not quite sure why we need the `CacheEntry` enum though. My best guess would be to release the `Task` to save some memory but @drexin should know.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [October 26, 2022, 10:29am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/10 "2022-10-26T10:29:02Z")

</div>

> [@Erik\_Aigner](#):
>
> What I actually need is a method that guarantees that a certain function call can not run in parallel

You can use an [AsyncSemaphore](https://github.com/groue/Semaphore):

```swift
actor MyActor {
  private let semaphore = AsyncSemaphore(value: 1)
  
  func call(_ i: Int) async {
    // Makes sure no two tasks can execute
    // call(_:) concurrently. 
    await semaphore.wait()
    defer { semaphore.signal() }
    
    print("---")
    print(i)
    await sleepRandom()
    print(i)
  }
}

```

And if you need to support cancellation:

```swift
func call(_ i: Int) async throws {
  // throws CancellationError if the current task is cancelled 
  try await semaphore.waitUnlessCancelled()
  defer { semaphore.signal() }
  ...
}

```

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 10:33am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/11 "2022-10-26T10:33:23Z")

</div>

`AsyncSemaphore` still uses a regular lock inside, so I would just move the problem from my code into the package.

It seems the sensible approach is to go back to `DispatchQueue`s and locking.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [October 26, 2022, 10:39am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/12 "2022-10-26T10:39:04Z")

</div>

> `AsyncSemaphore` still uses a regular lock inside

I still don't know if this is due to the [bad interaction between Xcode 14 and macOS before 13](https://forums.swift.org/t/concurrency-is-broken-in-xcode-14-for-macos/60294), or if this is strictly necessary. The people responsible for Swift concurrency won't discuss the proper way to implement a counting semaphore in a way that 1. fits their view of Swift concurrency, 2. back-deploys, 3. deals with Xcode 14 and macOS in the less painful way possible. ¯\_(ツ)\_/¯

Did I mention that testing such code is hum hum tricky, since iOS simulators have a limited thread pool, and macOS faces the difficulties linked above?

When locks can be removed, `AsyncSemaphore` will stop using them. So far, this code is reliable to my knowledge. It answers the OP's question, and beyond (cancellation).

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 10:41am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/13 "2022-10-26T10:41:40Z")

</div>

Yes, thank you - I was not criticising your answer - I was just saying that compiling `AsyncSemaphore` would give you the same warnigns as I encounter.

So the ball is in Apples court to provide a suitable primitive as an alternative.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [October 26, 2022, 10:43am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/14 "2022-10-26T10:43:19Z")

</div>

I did not emit any warning last time I compiled it.

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 10:45am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/15 "2022-10-26T10:45:04Z")

</div>

Probably because your `lock()` call is hidden behind an internal function

> func lock() { \_lock.lock() }

Also keep in mind this is an Xcode 14.1 RC1 warning (so maybe you still have 14.01)

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [October 26, 2022, 10:48am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/16 "2022-10-26T10:48:43Z")

</div>

> [@Erik\_Aigner](#):
>
> Probably because your `lock()` call is hidden behind an internal function

That's exactly how the warning is avoided, as designed by [SE-0340](https://github.com/apple/swift-evolution/blob/main/proposals/0340-swift-noasync.md):

> The `noasync` availability attribute only prevents API usage in the immediate asynchronous context; wrapping a call to an unavailable API in a synchronous context and calling the wrapper will not emit an error. This allows for cases where it is possible to use the API safely within an asynchronous context, but in specific ways.

The lock is not "hidden", it is "wrapped" 😉

---

<div class="post-metadata">

**Author:** ![tclementdev](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tclementdev/32/13398_2.png) [@tclementdev](https://forums.swift.org/u/tclementdev)\
**Post date:** [October 26, 2022, 11:29am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/17 "2022-10-26T11:29:29Z")

</div>

I think what's important here is that @gwendal.roue is not locking across an await, therefore he's doing a perfectly fine use of a lock (and honestly I don't even think it needs to be removed at some point) and solves the problem in a way your code doesn't.

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 11:43am UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/18 "2022-10-26T11:43:03Z")

</div>

I know his use is valid, that is also not what this thread is about.

This thread is about "how do I solve this with a standard Swift Concurrency primitive without including a several 100LOC 3rd party package".

If Apple introduces a warning, it should be a given there is an alternative solution in place that is 2 lines max.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [October 26, 2022, 12:43pm UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/19 "2022-10-26T12:43:16Z")

</div>

It's too early, Swift concurrency is too young, and basic building blocks for everyday coding aren't settled yet.That's why you see sample code pasted in forum posts, and a few third party libraries.

---

<div class="post-metadata">

**Author:** ![Erik\_Aigner](https://avatars.discourse-cdn.com/v4/letter/e/7c8e57/32.png) [@Erik\_Aigner](https://forums.swift.org/u/Erik_Aigner)\
**Post date:** [October 26, 2022, 1:18pm UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/20 "2022-10-26T13:18:59Z")

</div>

In case anyone is interested, here is the minimal `AsyncLock` I came up with, that is not an `actor`, and thus can be used inside a `defer` statement.

```swift
final class AsyncLock: @unchecked Sendable {

    private enum Locker {
        case holder
        case waiter(CheckedContinuation<Bool, Never>)
    }

    private var _lock = NSLock()
    private var lockers: [Locker] = []

    func lock() async {
        while true {
            let acquiredLock = await withCheckedContinuation { cont in
                _lock.lock()
                if lockers.isEmpty {
                    lockers.append(.holder)
                    cont.resume(returning: true)
                } else {
                    lockers.append(.waiter(cont))
                }
                _lock.unlock()
            }
            if acquiredLock {
                break
            }
        }
    }

    func unlock() {
        _lock.lock()
        assert(!lockers.isEmpty)
        for locker in lockers {
            if case let .waiter(cont) = locker {
                cont.resume(returning: false)
            }
        }
        lockers = []
        _lock.unlock()
    }
}

```

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [October 26, 2022, 4:18pm UTC](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029/21 "2022-10-26T16:18:25Z")

</div>

> [@Erik\_Aigner](#):
>
> ```swift
> func unlock() {
> _lock.lock()
> assert(!lockers.isEmpty)
> for locker in lockers {
> if case let .waiter(cont) = locker {
> cont.resume(returning: false)
> }
> }
> lockers = []
> _lock.unlock()
> }
> 
> ```

Are you sure it’s safe to resume the continuation while holding the lock? I think you want to unlock first.

[Next page](https://forums.swift.org/t/what-does-use-async-safe-scoped-locking-instead-even-mean/61029.md?page=2)
