// to a replacement owner, that owner's destructor
// must not run while we hold m_mutex.
shared_ptr<ExclusionOwner> replacement;
- list<Task> waiters;
+ Task waiter;
{
unique_lock<mutex> lock(m_state->m_mutex);
replacement = m_state->m_owner.lock();
// will wake the waiters.
return;
}
- waiters.swap(m_state->m_waiters);
+ if (m_state->m_waiters.empty()) {
+ return;
+ }
+ // Wake exactly one waiter. Waking all of them
+ // makes every release a broadcast, and the
+ // broadcasts feed each other: each woken task's
+ // failed batch reacquire transiently takes and
+ // releases locks, each such release re-wakes
+ // every waiter of that lock, and a handful of
+ // contending tasks can spin this way forever
+ // (observed as a livelock retrying ~55k/s).
+ // The woken task retries its whole memoized
+ // lock set, so locks it acquires and releases
+ // on the way to failure hand the wake onward;
+ // the remaining waiters are served one release
+ // at a time.
+ waiter = m_state->m_waiters.front();
+ m_state->m_waiters.pop_front();
}
// Wake outside the state mutex: Task::run() takes
// task-infrastructure locks of its own.
- for (const auto &t : waiters) {
- t.run();
- }
+ waiter.run();
}
};