scan_one_extent_item returned true for every Borrower::Dead, and the
scan task lambda responds to true with Task::current_task().run() —
an immediate unconditional self-reschedule. Planner tasks therefore
never actually waited on the Exclusion rendezvous that try_lock had
established for them: they polled. At endgame, when the surviving
work is a few overlapping ref clusters, four such tasks can occupy
the entire worker pool with retry churn (measured at ~55k restarts/s,
borrower_dead_reacquire=332M in one stalled run) while the dedupe
rate collapses.
Self-reschedule only for limited() restarts (hash insert conflicts),
which genuinely have no rendezvous. For lock contention, return
false and let the rendezvous wake the task when the lock's owning
task completes — the retry rate becomes bounded by the winner's
progress instead of by how fast the loser can spin.
An alternative was tried first: parking waiters on the Exclusion
itself and waking on final lock release (with both wake-all and
wake-one variants). Both livelocked precisely because of this
polling — the planner tasks never consumed their wakeups, and the
parked-waiter bookkeeping interacted badly with the retry storm.
Removing the poll attacks the actual defect.
Assisted-by: Claude-Code:claude-fable-5
BEESCOUNT(hash_insert_conflict_give_up);
return false;
}
+ // Limited restarts (hash insert conflicts) have no
+ // Exclusion rendezvous, so the caller must reschedule
+ // the task itself.
+ return true;
}
- return true;
+ // Lock contention: a rendezvous was established on the
+ // contended Exclusion when try_lock failed, and the Task
+ // infrastructure reschedules this task when the winning
+ // task completes. Self-rescheduling here as well turns
+ // every restart into an unthrottled poll — at endgame a
+ // handful of tasks contending on the same ref cluster
+ // can occupy the whole worker pool with retry churn
+ // (observed at ~55k retries/s).
+ return false;
}
}