/*:#localized(key: "ClassicBugUnbalancedEnterLeave")
 # The classic bug: unbalanced `enter`/`leave`

 A dispatch group is only as correct as your bookkeeping. The rule is
 absolute: **exactly one `leave()` for every `enter()`, on every path.** Break
 it in either direction and you get a distinct failure.

 Too few `leave()` and the group hangs. Miss a `leave()` on some code path
 and the counter never returns to zero. `notify` never fires; `wait()` waits
 forever.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let group = DispatchGroup()
let queue = DispatchQueue(label: "orders", attributes: .concurrent)

for drink in ["Latte", "Mocha", "Flat white"] {
    group.enter()
    queue.async {
        // Bug: this early return skips leave() for empty names.
        guard !drink.isEmpty else { return }
        print("✅ \(drink) ready")
        group.leave()
    }
}

group.notify(queue: .main) {
    print("This may never print if any path forgot to leave()")
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "ClassicBugUnbalancedEnterLeave-2")
 The names above aren't empty, so it happens to work, but the shape is the
 danger.

 * callout(Try it): Add an empty string `""` to the array and run it again. That
 empty name hits the `guard` and returns *without* calling `leave()`, so the
 group's counter never gets back to zero. The result: the three real drinks
 print, but `This may never print…` never does, and the page keeps running
 until you stop it yourself. That silent hang, with no crash and no error, just
 an "all done" that never arrives, is the whole bug.

 > The instant a `guard`, a `throw`, or an error branch returns
 > before `leave()`, that task never leaves the group, and your "all done"
 > callback silently never runs. **A `defer { group.leave() }` right after
 > `enter()` guarantees the leave on every exit path** and is the standard cure.

 Too many `leave()` creates a crash. Call `leave()` more times than you called
 `enter()` and the group's count goes negative, which traps at runtime with an
 error like *"Unbalanced call to dispatch_group_leave."* This usually comes
 from putting `enter()` in the wrong place: for example, calling it once
 *outside* the loop but `leave()` *inside* each iteration. The counter hits
 zero after the first task, `notify` **over-fires early** while other tasks
 are still running, and the extra leaves then crash. Keep `enter()` and
 `leave()` paired at the same level.

 [Previous](@previous)  |  page 5 of 8  |  [Next: DispatchSemaphore: Limiting How Many Run at Once](@next)
 */
