Fix a signal loss bug in `Eigen::EventCount::CancelWait` that leads to task stranding and deadlocks in thread pools under concurrent scheduling.
### Deadlock Scenario
`EventCount` maintains a packed 64-bit atomic state containing:
- `waiters`: count of threads preparing to wait (between `Prewait` and `CommitWait`/`CancelWait`).
- `signals`: count of pending wakeup notifications.
- `stack`: stack of parked threads sleeping on condition variables in `CommitWait`.
The deadlock occurs via the following race:
1. **Worker A parks**: Calls `Prewait()` then `CommitWait(&w_A)`. Worker A is added to `stack` and blocks (`waiters = 0, signals = 0, stack = [A]`).
2. **Worker B prepares to park**: Calls `Prewait()`. State becomes `waiters = 1, signals = 0, stack = [A]`.
3. **An external thread schedules a task**: Calls `Notify(false)` to wake a worker.
4. **`Notify(false)` sees `signals < waiters` (0 < 1)**: Instead of popping Worker A from `stack`, it increments `signals` from 0 to 1, intending for Worker B to consume the signal in `CommitWait()`.
5. **Worker B finds work and aborts parking**: Worker B calls `CancelWait()`.
6. **The Bug**: `CancelWait()` observes `waiters == signals` (1 == 1). To preserve the invariant `signals <= waiters`, it decrements both `waiters` and `signals` (`newstate -= kSignalInc`). This completely discards the notification! Worker A remains parked on `stack` indefinitely, even though work is queued in the pool. If another worker blocks waiting for work to complete, the entire pool deadlocks.
### Fix
When `CancelWait()` decrements `signals` because `waiters == signals`, set `notify = true` and call `Notify(false)` after the CAS succeeds. This forwards the consumed signal to unpark a waiting thread from `stack`.
### Testing
Added `test_cancel_wait_signal_forwarding()` in `threads_eventcount.cpp`, which deterministically reproduces this interleaving. Without the fix, the parked thread hangs and the test fails 100% of the time; with the fix, the test passes immediately.
PiperOrigin-RevId: 987583984
Change-Id: I6dc28ad1474a9ad2f9dea18e268838f0b1f76144
2 files changed