)]}'
{
  "commit": "6ec106f70acad8917d0e3825b4960f798d287f1f",
  "tree": "be5aa36a960c35fd486ae0c7f79dfdf831cdd3cb",
  "parents": [
    "7be6715efc96c1b29bbbda70e2209c59dd5c81dd"
  ],
  "author": {
    "name": "Todd Lipcon",
    "email": "tlipcon@google.com",
    "time": "Mon Oct 19 10:13:11 2026 -0700"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Mon Oct 19 10:14:03 2026 -0700"
  },
  "message": "Fix deadlock detection after ForgetDeadlockInfo() on a held Mutex\n\nIn debug builds, the Mutex deadlock detector records each lock order edge\n(A is held while B is acquired) in a global graph keyed by Mutex address.\nForgetDeadlockInfo() erases everything recorded about one Mutex by removing its\ngraph node. The header says to call it when the lock ordering of a Mutex\nchanges. ~Mutex() also calls it in debug builds, so that a new Mutex at a\nreused address does not inherit the old one\u0027s history.\n\nEach thread keeps a list of the locks it holds, so that it can add an edge from\neach of them when it acquires another lock. This list identified a held lock\nby the graph id that was looked up when the lock was acquired, and\nForgetDeadlockInfo() makes that id stale. So, before this change, if any thread\ncalled ForgetDeadlockInfo() on a Mutex while some thread held it:\n\n- The hold was ignored for lock ordering until it was released.\n- unlock() looked up the entry by a newly assigned id, so it never removed the\n  entry. After 40 leaked entries the list was full, and the detector silently\n  stopped checking that thread. For example, if a thread held a Mutex 50\n  times, and each time another thread forgot it, a later a -\u003e b, b -\u003e a\n  inversion on the first thread was not reported.\n- AssertNotHeld() compared a new id with the stale one, so it passed although\n  the thread held the Mutex.\n\nThese effects were accidental. This change identifies held locks by Mutex*\ninstead. ForgetDeadlockInfo() still removes the Mutex\u0027s graph node, and now:\n\n- If the calling thread holds the Mutex, that hold is still ignored for lock\n  ordering until it is released. A new per-entry flag marks such holds.\n- Holds by other threads keep counting. When such a thread next acquires\n  another lock, the detector looks up the Mutex\u0027s new graph id and records the\n  edge. This includes the later hold of a thread that was blocked in lock(), or\n  waiting in CondVar::Wait(), when the Mutex was forgotten.\n- unlock() always removes the entry, so the thread keeps being checked.\n- ~Mutex() also removes any entry for the Mutex on the calling thread, since a\n  Mutex destroyed while locked is never unlocked. Previously, destroying 40\n  locked Mutexes on a thread (as in Mutex.LockedMutexDestructionBug) filled the\n  list and disabled deadlock detection on that thread for the rest of the\n  process.\n- AssertNotHeld() fails for any Mutex that the thread holds, even after\n  ForgetDeadlockInfo().\n\nThese rules do not depend on when a graph id was looked up. This lets a\nfollow-up change look up graph ids only for nested locking, without changing\nbehavior.\n\nAs a side effect, unlock() and AssertNotHeld() no longer look up a graph id, so\nthey no longer take deadlock_graph_mu or create graph nodes. (Before, unlock()\neven created a new node for a forgotten Mutex.) So a lock()/unlock() pair now\ntakes deadlock_graph_mu once instead of twice. ~Mutex() still takes it once.\n\nCode that calls ForgetDeadlockInfo() on a Mutex that other threads hold may now\nget deadlock reports. First, those holds now count. Second, threads that used\nto stop being checked after 40 leaked entries keep being checked, which can\nalso surface real inversions that involve other Mutexes. This mainly affects\ncode that calls ForgetDeadlockInfo() right before every lock, to turn off\ndeadlock detection for one Mutex.\n\nPiperOrigin-RevId: 1002421692\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "d6e2aace10c7f0cb7fffad116bc9ade8a72f5544",
      "old_mode": 33188,
      "old_path": "absl/synchronization/mutex.cc",
      "new_id": "7d5aba77acc9da2a075b0e184ca575865aef7875",
      "new_mode": 33188,
      "new_path": "absl/synchronization/mutex.cc"
    },
    {
      "type": "modify",
      "old_id": "a96bcff7ebf8414f0b25c00b47b1f65c643fc3a1",
      "old_mode": 33188,
      "old_path": "absl/synchronization/mutex_test.cc",
      "new_id": "e945d5e6664e663c0fd15822664b593d689cab6b",
      "new_mode": 33188,
      "new_path": "absl/synchronization/mutex_test.cc"
    }
  ]
}
