Actor Reentrancy

I might be wrong now, but using a DispatchSemaphore in an actor has proved to be problematic in the past when I was trying to do something similar.

I think the work around was to use continuations instead. (When I find the example code, I will add it here.)

@John_McCall, could you shed some light on this to educate us please?

Code Using Continuations
// Channel.swift

extension V3 {
    actor Channel<T> {
        private var store   = [T?] ()
        private var waiters = [UnsafeContinuation <T?, Never>] ()

        func put (_ u: T?) {
            store.append(u)
            if ! waiters.isEmpty {
                waiters.removeFirst().resume (returning: store.removeFirst ())
            }
        }
        
        func get () async -> T? {
            if store.isEmpty {
                return await withUnsafeContinuation {
                    waiters.append ($0)
                }
            }
            else {
                let value = store.removeFirst()
                return value
            }
        }
        
        var state: (packets: Int, waiters: Int) {
            return (store.count, waiters.count)
        }
        
        var description: String {
            return "\(state)"
        }
    }
}

Original Example

Source

actor Semaphore {
    private var count: Int
    private var waiters: [CheckedContinuation<Void, Never>] = []

    init(count: Int = 0) {
        self.count = count
    }

    func wait() async {
        count -= 1
        if count >= 0 { return }
        await withCheckedContinuation {
            waiters.append($0)
        }        
    }

    func release(count: Int = 1) {
        assert(count >= 1)
        self.count += count
        for _ in 0..<count {
            if waiters.isEmpty { return }
            waiters.removeFirst().resume()
        }
    }
}
1 Like