Mutex: Fix stall on single-core systems

On single-core systems, a thread could be preempted while holding an
absl::Mutex, or even worse, the spin lock. If a FIFO thread wakes up and
tries to acquire this lock, it might not be able to yield() to the sleeping
thread.

Within MutexDelay(), a yield() and a sleep(10us) are used to yield the CPU.
The yield() would do nothing if the calling thread holds the highest
priority in the system. The 10us sleep() may not be able to reach the
scheduler either, if the system is slow enough.
This code path is known to be reachable in the following scenarios:
- a FIFO thread calls LockSlowLoop() with spin lock held by a normal thread
- a FIFO thread calls LockWhen*() with the Mutex held by a normal thread for a long time
- a FIFO thread calls Await*(), releases the Mutex to be held by a normal thread for a long time

This CL adds a mutex global for the sleep time, and sets it using the
return time of the a yield() call. Yield() must reach the
scheduler even when it fails to yield to anyone, and would allow sleep() to do the
same. A small constant multiplier (5) is also applied to overcome uncontrollable
factors in the runtime and help sleep() to consistently yield to another thread.
Upper and lower bounds for the sleep time is also controlled to block any unreasonable values.

PiperOrigin-RevId: 483459711
Change-Id: I14efadbadaf9244a2462f377b515147bda651c89
pull/1305/head
Abseil Team 2 years ago committed by Copybara-Service
parent cb436cf014
commit b3e64c4168
  1. 34
      absl/synchronization/mutex.cc

@ -135,25 +135,42 @@ enum DelayMode { AGGRESSIVE, GENTLE };
struct ABSL_CACHELINE_ALIGNED MutexGlobals {
absl::once_flag once;
int spinloop_iterations = 0;
int32_t mutex_sleep_limit[2] = {};
int32_t mutex_sleep_spins[2] = {};
absl::Duration mutex_sleep_time;
};
absl::Duration MeasureTimeToYield() {
absl::Time before = absl::Now();
ABSL_INTERNAL_C_SYMBOL(AbslInternalMutexYield)();
return absl::Now() - before;
}
const MutexGlobals &GetMutexGlobals() {
ABSL_CONST_INIT static MutexGlobals data;
absl::base_internal::LowLevelCallOnce(&data.once, [&]() {
const int num_cpus = absl::base_internal::NumCPUs();
data.spinloop_iterations = num_cpus > 1 ? 1500 : 0;
// If this a uniprocessor, only yield/sleep. Otherwise, if the mode is
// If this a uniprocessor, only yield/sleep.
// Real-time threads are often unable to yield, so the sleep time needs
// to be long enough to keep the calling thread asleep until scheduling
// happens.
// If this is multiprocessor, allow spinning. If the mode is
// aggressive then spin many times before yielding. If the mode is
// gentle then spin only a few times before yielding. Aggressive spinning
// is used to ensure that an Unlock() call, which must get the spin lock
// for any thread to make progress gets it without undue delay.
if (num_cpus > 1) {
data.mutex_sleep_limit[AGGRESSIVE] = 5000;
data.mutex_sleep_limit[GENTLE] = 250;
data.mutex_sleep_spins[AGGRESSIVE] = 5000;
data.mutex_sleep_spins[GENTLE] = 250;
data.mutex_sleep_time = absl::Microseconds(10);
} else {
data.mutex_sleep_limit[AGGRESSIVE] = 0;
data.mutex_sleep_limit[GENTLE] = 0;
data.mutex_sleep_spins[AGGRESSIVE] = 0;
data.mutex_sleep_spins[GENTLE] = 0;
data.mutex_sleep_time = MeasureTimeToYield() * 5;
data.mutex_sleep_time =
std::min(data.mutex_sleep_time, absl::Milliseconds(1));
data.mutex_sleep_time =
std::max(data.mutex_sleep_time, absl::Microseconds(10));
}
});
return data;
@ -164,7 +181,8 @@ namespace synchronization_internal {
// Returns the Mutex delay on iteration `c` depending on the given `mode`.
// The returned value should be used as `c` for the next call to `MutexDelay`.
int MutexDelay(int32_t c, int mode) {
const int32_t limit = GetMutexGlobals().mutex_sleep_limit[mode];
const int32_t limit = GetMutexGlobals().mutex_sleep_spins[mode];
const absl::Duration sleep_time = GetMutexGlobals().mutex_sleep_time;
if (c < limit) {
// Spin.
c++;
@ -177,7 +195,7 @@ int MutexDelay(int32_t c, int mode) {
c++;
} else {
// Then wait.
absl::SleepFor(absl::Microseconds(10));
absl::SleepFor(sleep_time);
c = 0;
}
ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0);

Loading…
Cancel
Save