Skip to content
Commitwise
Menu

What is actor reentrancy in Swift, and how can it cause bugs?

iOSSenior

In shortAn actor runs one piece of its code at a time, but every await inside an actor method is a point where other calls can run. State read before an await may have changed after it, causing lost updates and duplicate work.

Model answer

Actors prevent data races: only one piece of actor-isolated code runs at a time. But they’re reentrant: when an actor method suspends at an await, the actor is free to run other calls, and when the method resumes, the state may be different.

actor ImageCache {
    private var images: [URL: Data] = [:]

    func image(for url: URL) async throws -> Data {
        if let cached = images[url] { return cached }
        let data = try await download(url)      // other calls run here
        images[url] = data                      // two callers can both reach this
        return data
    }
}

Ten rows asking for the same avatar all miss the cache before the first download finishes, so the same file is downloaded ten times. A read-modify-write across an await (let v = count; await …; count = v + 1) loses updates the same way.

Fixes:

  • Don’t hold state across await: do the read-modify-write synchronously, or re-read after resuming.
  • Record intent before suspending: store the in-flight Task in the dictionary before awaiting it, so later callers join it.
  • Keep critical sections synchronous: if a change must be atomic, do it without suspension points.

Swift made actors reentrant on purpose: non-reentrant actors waiting on each other would deadlock. The trade-off is that you reason about each await as a place where the world can change.

What they're looking for

A precise explanation (serialized, not transactional), a concrete example like a cache that downloads the same URL twice, and fixes (no state held across await, re-check after suspension, storing in-flight tasks). Bonus for explaining why actors are reentrant (to avoid deadlocks).

Common mistakes

  • Claiming actors make whole methods atomic.
  • Proposing a lock held across an await as the fix.
  • Not distinguishing a data race (prevented) from a logical race (still possible).

More ios questions

Esc

Type to search the whole site.