Skip to content

Commit 6d41348

Browse files
derekmaurocopybara-github
authored andcommitted
Synchronization: Add support for true relative timeouts using
monotonic clocks on Linux when the implementation uses futexes After this change, when synchronization methods that wait are passed an absl::Duration to limit the wait time, these methods will wait for that interval, even if the system clock is changed (subject to any limitations with how CLOCK_MONOTONIC keeps track of time). In other words, an observer measuring the time with a stop watch will now see the correct interval, even if the system clock is changed. Previously, the duration was added to the current time, and methods would wait until that time was reached on the possibly changed realtime system clock. The behavior of the synchronization methods that take an absl::Time is unchanged. These methods always wait until the absolute point in time is reached and respect changes to the system clock. In other words, an observer will always see the timeout occur when a wall clock reaches that time, even if the clock is manipulated externally. Note: ABSL_PREDICT_FALSE was removed from the error case in Futex as timeouts are handled by this case, and timeouts are part of normal operation. PiperOrigin-RevId: 516534869 Change-Id: Ib70b83e4be3f9e3f1727646975a21a1d30acb242
1 parent 6db185d commit 6d41348

File tree

3 files changed

+84
-46
lines changed

3 files changed

+84
-46
lines changed

absl/synchronization/internal/futex.h

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
#include "absl/base/config.h"
1818

19-
#ifdef _WIN32
20-
#include <windows.h>
21-
#else
19+
#ifndef _WIN32
2220
#include <sys/time.h>
2321
#include <unistd.h>
2422
#endif
@@ -85,34 +83,70 @@ namespace synchronization_internal {
8583

8684
class FutexImpl {
8785
public:
88-
static int WaitUntil(std::atomic<int32_t> *v, int32_t val,
86+
// Atomically check that `*v == val`, and if it is, then sleep until the
87+
// timeout `t` has been reached, or until woken by `Wake()`.
88+
static int WaitUntil(std::atomic<int32_t>* v, int32_t val,
8989
KernelTimeout t) {
90-
long err = 0; // NOLINT(runtime/int)
91-
if (t.has_timeout()) {
92-
// https://locklessinc.com/articles/futex_cheat_sheet/
93-
// Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time.
94-
struct timespec abs_timeout = t.MakeAbsTimespec();
95-
// Atomically check that the futex value is still 0, and if it
96-
// is, sleep until abs_timeout or until woken by FUTEX_WAKE.
97-
err = syscall(
98-
SYS_futex, reinterpret_cast<int32_t *>(v),
99-
FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME, val,
100-
&abs_timeout, nullptr, FUTEX_BITSET_MATCH_ANY);
90+
// Monotonic waits are disabled for production builds because go/btm
91+
// requires synchronized clocks.
92+
// TODO(b/160682823): Find a way to enable this when BTM is not enabled
93+
// since production builds don't always run on Borg.
94+
#if defined(CLOCK_MONOTONIC) && !defined(__GOOGLE_GRTE_VERSION__)
95+
constexpr bool kRelativeTimeoutSupported = true;
96+
#else
97+
constexpr bool kRelativeTimeoutSupported = false;
98+
#endif
99+
100+
if (!t.has_timeout()) {
101+
return Wait(v, val);
102+
} else if (kRelativeTimeoutSupported && t.is_relative_timeout()) {
103+
auto rel_timespec = t.MakeRelativeTimespec();
104+
return WaitRelativeTimeout(v, val, &rel_timespec);
101105
} else {
102-
// Atomically check that the futex value is still 0, and if it
103-
// is, sleep until woken by FUTEX_WAKE.
104-
err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v),
105-
FUTEX_WAIT | FUTEX_PRIVATE_FLAG, val, nullptr);
106+
auto abs_timespec = t.MakeAbsTimespec();
107+
return WaitAbsoluteTimeout(v, val, &abs_timespec);
106108
}
107-
if (ABSL_PREDICT_FALSE(err != 0)) {
109+
}
110+
111+
// Atomically check that `*v == val`, and if it is, then sleep until the until
112+
// woken by `Wake()`.
113+
static int Wait(std::atomic<int32_t>* v, int32_t val) {
114+
return WaitAbsoluteTimeout(v, val, nullptr);
115+
}
116+
117+
// Atomically check that `*v == val`, and if it is, then sleep until
118+
// CLOCK_REALTIME reaches `*abs_timeout`, or until woken by `Wake()`.
119+
static int WaitAbsoluteTimeout(std::atomic<int32_t>* v, int32_t val,
120+
const struct timespec* abs_timeout) {
121+
// https://locklessinc.com/articles/futex_cheat_sheet/
122+
// Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time.
123+
auto err =
124+
syscall(SYS_futex, reinterpret_cast<int32_t*>(v),
125+
FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME,
126+
val, abs_timeout, nullptr, FUTEX_BITSET_MATCH_ANY);
127+
if (err != 0) {
128+
return -errno;
129+
}
130+
return 0;
131+
}
132+
133+
// Atomically check that `*v == val`, and if it is, then sleep until
134+
// `*rel_timeout` has elapsed, or until woken by `Wake()`.
135+
static int WaitRelativeTimeout(std::atomic<int32_t>* v, int32_t val,
136+
const struct timespec* rel_timeout) {
137+
// Atomically check that the futex value is still 0, and if it
138+
// is, sleep until abs_timeout or until woken by FUTEX_WAKE.
139+
auto err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v),
140+
FUTEX_PRIVATE_FLAG, val, rel_timeout);
141+
if (err != 0) {
108142
return -errno;
109143
}
110144
return 0;
111145
}
112146

113-
static int Wake(std::atomic<int32_t> *v, int32_t count) {
114-
// NOLINTNEXTLINE(runtime/int)
115-
long err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v),
147+
// Wakes at most `count` waiters that have entered the sleep state on `v`.
148+
static int Wake(std::atomic<int32_t>* v, int32_t count) {
149+
auto err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v),
116150
FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count);
117151
if (ABSL_PREDICT_FALSE(err < 0)) {
118152
return -errno;

absl/synchronization/internal/waiter.cc

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
#include "absl/base/optimization.h"
4949
#include "absl/synchronization/internal/kernel_timeout.h"
5050

51-
5251
namespace absl {
5352
ABSL_NAMESPACE_BEGIN
5453
namespace synchronization_internal {
@@ -67,9 +66,7 @@ static void MaybeBecomeIdle() {
6766

6867
#if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX
6968

70-
Waiter::Waiter() {
71-
futex_.store(0, std::memory_order_relaxed);
72-
}
69+
Waiter::Waiter() : futex_(0) {}
7370

7471
bool Waiter::Wait(KernelTimeout t) {
7572
// Loop until we can atomically decrement futex from a positive

absl/synchronization/mutex.cc

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -635,21 +635,6 @@ void Mutex::InternalAttemptToUseMutexInFatalSignalHandler() {
635635
std::memory_order_release);
636636
}
637637

638-
// --------------------------time support
639-
640-
// Return the current time plus the timeout. Use the same clock as
641-
// PerThreadSem::Wait() for consistency. Unfortunately, we don't have
642-
// such a choice when a deadline is given directly.
643-
static absl::Time DeadlineFromTimeout(absl::Duration timeout) {
644-
#ifndef _WIN32
645-
struct timeval tv;
646-
gettimeofday(&tv, nullptr);
647-
return absl::TimeFromTimeval(tv) + timeout;
648-
#else
649-
return absl::Now() + timeout;
650-
#endif
651-
}
652-
653638
// --------------------------Mutexes
654639

655640
// In the layout below, the msb of the bottom byte is currently unused. Also,
@@ -1549,7 +1534,13 @@ void Mutex::LockWhen(const Condition &cond) {
15491534
}
15501535

15511536
bool Mutex::LockWhenWithTimeout(const Condition &cond, absl::Duration timeout) {
1552-
return LockWhenWithDeadline(cond, DeadlineFromTimeout(timeout));
1537+
ABSL_TSAN_MUTEX_PRE_LOCK(this, 0);
1538+
GraphId id = DebugOnlyDeadlockCheck(this);
1539+
bool res = LockSlowWithDeadline(kExclusive, &cond,
1540+
KernelTimeout(timeout), 0);
1541+
DebugOnlyLockEnter(this, id);
1542+
ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0);
1543+
return res;
15531544
}
15541545

15551546
bool Mutex::LockWhenWithDeadline(const Condition &cond, absl::Time deadline) {
@@ -1572,7 +1563,12 @@ void Mutex::ReaderLockWhen(const Condition &cond) {
15721563

15731564
bool Mutex::ReaderLockWhenWithTimeout(const Condition &cond,
15741565
absl::Duration timeout) {
1575-
return ReaderLockWhenWithDeadline(cond, DeadlineFromTimeout(timeout));
1566+
ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock);
1567+
GraphId id = DebugOnlyDeadlockCheck(this);
1568+
bool res = LockSlowWithDeadline(kShared, &cond, KernelTimeout(timeout), 0);
1569+
DebugOnlyLockEnter(this, id);
1570+
ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0);
1571+
return res;
15761572
}
15771573

15781574
bool Mutex::ReaderLockWhenWithDeadline(const Condition &cond,
@@ -1597,7 +1593,18 @@ void Mutex::Await(const Condition &cond) {
15971593
}
15981594

15991595
bool Mutex::AwaitWithTimeout(const Condition &cond, absl::Duration timeout) {
1600-
return AwaitWithDeadline(cond, DeadlineFromTimeout(timeout));
1596+
if (cond.Eval()) { // condition already true; nothing to do
1597+
if (kDebugMode) {
1598+
this->AssertReaderHeld();
1599+
}
1600+
return true;
1601+
}
1602+
1603+
KernelTimeout t{timeout};
1604+
bool res = this->AwaitCommon(cond, t);
1605+
ABSL_RAW_CHECK(res || t.has_timeout(),
1606+
"condition untrue on return from Await");
1607+
return res;
16011608
}
16021609

16031610
bool Mutex::AwaitWithDeadline(const Condition &cond, absl::Time deadline) {
@@ -2663,7 +2670,7 @@ bool CondVar::WaitCommon(Mutex *mutex, KernelTimeout t) {
26632670
}
26642671

26652672
bool CondVar::WaitWithTimeout(Mutex *mu, absl::Duration timeout) {
2666-
return WaitWithDeadline(mu, DeadlineFromTimeout(timeout));
2673+
return WaitCommon(mu, KernelTimeout(timeout));
26672674
}
26682675

26692676
bool CondVar::WaitWithDeadline(Mutex *mu, absl::Time deadline) {

0 commit comments

Comments
 (0)