)]}'
{
  "commit": "aecd9b8ceaf389fa43c981de9990a391ce42bafb",
  "tree": "c0da1ef1f01bfac2de9e34e6d5dffcb67332b1c3",
  "parents": [
    "746fe26e4c097835f874a7f43ccd94d7ba6d5bd9"
  ],
  "author": {
    "name": "Junwhan Ahn",
    "email": "junwhan@google.com",
    "time": "Thu Sep 24 10:07:45 2026 -0700"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Thu Sep 24 10:08:25 2026 -0700"
  },
  "message": "Fix a signal loss bug in `Eigen::EventCount::CancelWait` that leads to task stranding and deadlocks in thread pools under concurrent scheduling.\n\n### Deadlock Scenario\n`EventCount` maintains a packed 64-bit atomic state containing:\n- `waiters`: count of threads preparing to wait (between `Prewait` and `CommitWait`/`CancelWait`).\n- `signals`: count of pending wakeup notifications.\n- `stack`: stack of parked threads sleeping on condition variables in `CommitWait`.\n\nThe deadlock occurs via the following race:\n1. **Worker A parks**: Calls `Prewait()` then `CommitWait(\u0026w_A)`. Worker A is added to `stack` and blocks (`waiters \u003d 0, signals \u003d 0, stack \u003d [A]`).\n2. **Worker B prepares to park**: Calls `Prewait()`. State becomes `waiters \u003d 1, signals \u003d 0, stack \u003d [A]`.\n3. **An external thread schedules a task**: Calls `Notify(false)` to wake a worker.\n4. **`Notify(false)` sees `signals \u003c waiters` (0 \u003c 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()`.\n5. **Worker B finds work and aborts parking**: Worker B calls `CancelWait()`.\n6. **The Bug**: `CancelWait()` observes `waiters \u003d\u003d signals` (1 \u003d\u003d 1). To preserve the invariant `signals \u003c\u003d waiters`, it decrements both `waiters` and `signals` (`newstate -\u003d 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.\n\n### Fix\nWhen `CancelWait()` decrements `signals` because `waiters \u003d\u003d signals`, set `notify \u003d true` and call `Notify(false)` after the CAS succeeds. This forwards the consumed signal to unpark a waiting thread from `stack`.\n\n### Testing\nAdded `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.\n\nPiperOrigin-RevId: 987583984\nChange-Id: I6dc28ad1474a9ad2f9dea18e268838f0b1f76144\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "6eda6f4b632edd5d6deb4f05b01057d102055f00",
      "old_mode": 33188,
      "old_path": "Eigen/src/ThreadPool/EventCount.h",
      "new_id": "c4e73cfe7fe12ca1d286db3ae892bd72c0a854b4",
      "new_mode": 33188,
      "new_path": "Eigen/src/ThreadPool/EventCount.h"
    },
    {
      "type": "modify",
      "old_id": "ebac0919f1070c8611148e134465013d8f9ae2f8",
      "old_mode": 33188,
      "old_path": "test/threads_eventcount.cpp",
      "new_id": "a40bd0070c7220199705dd19d5a7dce4ded8df54",
      "new_mode": 33188,
      "new_path": "test/threads_eventcount.cpp"
    }
  ]
}
