Actors and DispatchSemaphores not working together

Why don't actors and DispatchSemaphores work together?

I have discovered this when I was trying out the following code originally posted by @nikolai.ruhe

actor MeetingPoint {
    var completion: CheckedContinuation<Void, Never>? = nil

    func join() async {
        if let completion = completion {
            self.completion = nil
            completion.resume()
        } else {
            await withCheckedContinuation {
                self.completion = $0
            }
        }
    }
}

I first, naively, used an actor and a DispatchSemaphore to create an EventFlag.

actor EventFlag
 func log (_ s: String) {
    print ("Using an actor: \(s)")
}

actor EventFlag {
    private var max       = 0
    private var value     = 0
    private var semaphore = DispatchSemaphore (value: 0)

    init (max: Int) {
        self.max = max
    }
    
    func set () {
        value += 1
        if value >= max {
            semaphore.signal()
        }
    }
    
    func wait () {
        if value < max {
            semaphore.wait()
        }
    }
}

The driver:


let mp = MeetingPoint ()
let ef = EventFlag (max: 2)

Task {
    await mp.join ()
    print ("joined 1")
    await ef.set()
}

Task {
    await mp.join ()
    print ("joined 2")
    await ef.set()
}

log ("wait...")
await ef.wait ()
log ("done.")

The output:

Using an actor: wait...
joined 2
joined 1

From the output, I can tell that my actor EventFlag does not work as intended.

However, if I use a class instead, I get the desired result.

class EventFlag
func log (_ s: String) {
    print ("Using a class instead: \(s)")
}

class EventFlag {
    private var max   = 0
    private var value = 0
    private var lock  = DispatchSemaphore (value: 1)
    private var flag  = DispatchSemaphore (value: 0)

    init (max: Int) {
        self.max = max
    }
    
    func set () async {
        lock.wait()
        value += 1
        if value >= max {
            flag.signal()
        }
        lock.signal()
    }
    
    func wait () async {
        flag.wait()
    }
}

The output:

Using a class instead: wait...
joined 2
joined 1
Using a class instead: done.

Obviously I am doing something wrong in my actor EventFlag, but I can't figure it out.

Could anyone help me understand what I am doing wrong?

Update:

The following version with CheckedContinuation seems to work, but I am not sure if it is correct.

actor EventFlag
actor EventFlag {
    private var max       = 0
    private var value     = 0
    private var done      = false
    private var completion: CheckedContinuation <Void, Never>? = nil
    
    init (max: Int) {
        self.max = max
    }
    
    func set () {
        value += 1
        if value >= max {
            done = true;
            if let completion {
                completion.resume()
            }
        }
    }
    
    func wait () async {
        if !done {
            if value < max {
                await withCheckedContinuation {
                    self.completion = $0
                }
            }
        }
    }
}