Skip to content

Commit 97ffc69

Browse files
bcaimanoevergreen
authored andcommitted
SERVER-43800 ClockSource::waitForConditionUntil shouldn't use unique_lock out of line
1 parent 906ac3c commit 97ffc69

File tree

4 files changed

+43
-35
lines changed

4 files changed

+43
-35
lines changed

src/mongo/transport/baton_asio_linux.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ class TransportLayerASIO::BatonASIO : public NetworkingBaton {
305305
// If we don't have a timeout, or we have a timeout that's unexpired, run poll.
306306
if (!deadline || (*deadline > now)) {
307307
if (deadline && !clkSource->tracksSystemClock()) {
308-
invariant(clkSource->setAlarm(*deadline, [this] { notify(); }));
308+
invariant(clkSource->setAlarm(*deadline,
309+
[this, anchor = shared_from_this()] { notify(); }));
309310

310311
deadline.reset();
311312
}

src/mongo/util/clock_source.cpp

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,61 +35,59 @@
3535

3636
namespace mongo {
3737
stdx::cv_status ClockSource::waitForConditionUntil(stdx::condition_variable& cv,
38-
BasicLockableAdapter m,
38+
BasicLockableAdapter bla,
3939
Date_t deadline,
4040
Waitable* waitable) {
4141
if (_tracksSystemClock) {
4242
if (deadline == Date_t::max()) {
43-
Waitable::wait(waitable, this, cv, m);
43+
Waitable::wait(waitable, this, cv, bla);
4444
return stdx::cv_status::no_timeout;
4545
}
4646

47-
return Waitable::wait_until(waitable, this, cv, m, deadline.toSystemTimePoint());
47+
return Waitable::wait_until(waitable, this, cv, bla, deadline.toSystemTimePoint());
4848
}
4949

5050
// The rest of this function only runs during testing, when the clock source is virtualized and
5151
// does not track the system clock.
5252

53-
if (deadline <= now()) {
53+
auto currentTime = now();
54+
if (deadline <= currentTime) {
5455
return stdx::cv_status::timeout;
5556
}
5657

5758
struct AlarmInfo {
58-
Mutex controlMutex = MONGO_MAKE_LATCH("AlarmInfo::controlMutex");
59-
boost::optional<BasicLockableAdapter> waitLock;
60-
stdx::condition_variable* waitCV;
61-
stdx::cv_status cvWaitResult = stdx::cv_status::no_timeout;
59+
stdx::mutex mutex; // NOLINT
60+
61+
stdx::condition_variable* cv;
62+
stdx::cv_status result = stdx::cv_status::no_timeout;
6263
};
6364
auto alarmInfo = std::make_shared<AlarmInfo>();
64-
alarmInfo->waitCV = &cv;
65-
alarmInfo->waitLock = m;
66-
const auto waiterThreadId = stdx::this_thread::get_id();
67-
bool invokedAlarmInline = false;
68-
invariant(setAlarm(deadline, [alarmInfo, waiterThreadId, &invokedAlarmInline] {
69-
stdx::lock_guard<Latch> controlLk(alarmInfo->controlMutex);
70-
alarmInfo->cvWaitResult = stdx::cv_status::timeout;
71-
if (!alarmInfo->waitLock) {
72-
return;
73-
}
74-
if (stdx::this_thread::get_id() == waiterThreadId) {
75-
// In NetworkInterfaceMock, setAlarm may invoke its callback immediately if the deadline
76-
// has expired, so we detect that case and avoid self-deadlock by returning early, here.
77-
// It is safe to set invokedAlarmInline without synchronization in this case, because it
78-
// is exactly the case where the same thread is writing and consulting the value.
79-
invokedAlarmInline = true;
65+
alarmInfo->cv = &cv;
66+
67+
invariant(setAlarm(deadline, [alarmInfo] {
68+
// Set an alarm to hit our virtualized deadline
69+
stdx::lock_guard infoLk(alarmInfo->mutex);
70+
auto cv = std::exchange(alarmInfo->cv, nullptr);
71+
if (!cv) {
8072
return;
8173
}
82-
stdx::lock_guard<BasicLockableAdapter> waitLk(*alarmInfo->waitLock);
83-
alarmInfo->waitCV->notify_all();
74+
75+
alarmInfo->result = stdx::cv_status::timeout;
76+
cv->notify_all();
8477
}));
85-
if (!invokedAlarmInline) {
86-
Waitable::wait(waitable, this, cv, m);
78+
79+
if (stdx::lock_guard infoLk(alarmInfo->mutex); !alarmInfo->cv) {
80+
// If setAlarm() ran inline, then we've timed out
81+
return alarmInfo->result;
8782
}
88-
m.unlock();
89-
stdx::lock_guard<Latch> controlLk(alarmInfo->controlMutex);
90-
m.lock();
91-
alarmInfo->waitLock = boost::none;
92-
alarmInfo->waitCV = nullptr;
93-
return alarmInfo->cvWaitResult;
83+
84+
// This is a wait_until because theoretically setAlarm could run out of line before this cv
85+
// joins the wait list. Then it could completely miss the notification and block until a lucky
86+
// renotify or spurious wakeup.
87+
Waitable::wait_until(waitable, this, cv, bla, currentTime + kMaxTimeoutForArtificialClocks);
88+
89+
stdx::lock_guard infoLk(alarmInfo->mutex);
90+
alarmInfo->cv = nullptr;
91+
return alarmInfo->result;
9492
}
9593
} // namespace mongo

src/mongo/util/clock_source.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class ClockSource {
5555
std::is_function<std::remove_pointer_t<PredicateT>>::value> {
5656
};
5757

58+
static constexpr auto kMaxTimeoutForArtificialClocks = Seconds(1);
59+
5860
public:
5961
virtual ~ClockSource() = default;
6062

@@ -90,6 +92,10 @@ class ClockSource {
9092
/**
9193
* Like cv.wait_until(m, deadline), but uses this ClockSource instead of
9294
* stdx::chrono::system_clock to measure the passage of time.
95+
*
96+
* Note that this can suffer spurious wakeups like cw.wait_until() and, when used with a mocked
97+
* clock source, may sleep in system time for kMaxTimeoutForArtificialClocks due to unfortunate
98+
* implementation details.
9399
*/
94100
stdx::cv_status waitForConditionUntil(stdx::condition_variable& cv,
95101
BasicLockableAdapter m,

src/mongo/util/waitable.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ namespace mongo {
4646
*
4747
* The current implementer of Waitable is the transport layer baton type, which performs delayed IO
4848
* when it would otherwise block.
49+
*
50+
* Note that every Waitable should be level-triggered like its base class, Notifyable. See
51+
* mongo/stdx/condition_variable.h for more details.
4952
*/
5053
class Waitable : public Notifyable {
5154
public:

0 commit comments

Comments
 (0)