summaryrefslogtreecommitdiff
path: root/absl/synchronization/mutex.cc
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2022-10-24 12:48:38 -0700
committerGravatar Copybara-Service <copybara-worker@google.com>2022-10-24 12:49:43 -0700
commitb3e64c416844c3579f90f4b1cc42d8d137f21b0a (patch)
tree4adc0d4745062174ea24ec3437edee6bdae26c5c /absl/synchronization/mutex.cc
parentcb436cf0142b4cbe47aae94223443df7f82e2920 (diff)
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
Diffstat (limited to 'absl/synchronization/mutex.cc')
-rw-r--r--absl/synchronization/mutex.cc34
1 files changed, 26 insertions, 8 deletions
diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc
index 69103ed8..b0f412bf 100644
--- a/absl/synchronization/mutex.cc
+++ b/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);