-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathsimulated_clock.cc
More file actions
225 lines (192 loc) · 7.23 KB
/
simulated_clock.cc
File metadata and controls
225 lines (192 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright 2026 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/time/simulated_clock.h"
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "absl/base/config.h"
#include "absl/base/macros.h"
#include "absl/base/nullability.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
// There are a few tricky details in the implementation of SimulatedClock.
//
// The external mutex that is passed as a param of AwaitWithDeadline() is not
// under the control of SimulatedClock; in particular, it can be destroyed any
// time after AwaitWithDeadline() returns. This requires the wakeup call from
// AdvanceTime() to avoid grabbing the external mutex if AwaitWithDeadline()
// has returned. This is accomplished by allowing the waiter to be cancelled
// via a bool guarded by a mutex in WakeUpInfo.
//
// Once AwaitWithDeadline() has released lock_, someone nefarious might call
// the SimulatedClock destructor, so it isn't possible for AwaitWithDeadline()
// to remove the wakeup call from waiters_ if the condition it is awaiting
// becomes true; it cancels the waiter instead. This means that,
// theoretically, many obsolete entries could pile up in waiters_ if
// AwaitWithDeadline() keeps being called but simulated time is not advanced.
// This seems unlikely to happen in practice.
//
// The WakeUpInfo in waiters_ are always awoken via WakeUp() (and
// removed) or cancelled before AwaitWithDeadline() returns. If a
// waiters_ value is cancelled then calling its WakeUp() method will
// short-circuit before touching the external mutex.
class SimulatedClock::WakeUpInfo {
public:
WakeUpInfo(absl::Mutex* mu, absl::Condition cond)
: mu_(mu),
cond_(cond),
wakeup_time_passed_(false),
cancelled_(false),
wakeup_called_(false) {}
void WakeUp() {
// If we are cancelled then AwaitWithDeadline may have returned, in which
// case we can't lock mu_.
{
absl::MutexLock lock(cancellation_mu_);
if (cancelled_) return;
wakeup_called_ = true;
}
absl::MutexLock lock(*mu_);
wakeup_time_passed_ = true;
}
void AwaitConditionOrWakeUp() {
mu_->Await(absl::Condition(this, &WakeUpInfo::Ready));
}
void CancelOrAwaitWakeUp() {
bool wakeup_called;
{
absl::MutexLock lock(cancellation_mu_);
cancelled_ = true;
wakeup_called = wakeup_called_;
}
if (wakeup_called && !wakeup_time_passed_) {
// Wait for WakeUp to complete.
//
// Note that this will unlock 'mu_'; this is actually necessary
// so that WakeUp() can unblock and complete. This does allow
// for 'cond_' to potentially change from true to false; that is
// OK, since WakeUp() is being called, so the deadline must be
// past, and so this method is fulfilling its duties. (Well, the
// destructor might be calling WakeUp(), but if you're deleting
// time itself while waiting for a deadline to pass, you deserve
// what you get.)
mu_->Await(absl::Condition(&wakeup_time_passed_));
}
}
private:
bool Ready() const { return wakeup_time_passed_ || cond_.Eval(); }
absl::Mutex* mu_;
absl::Condition cond_;
bool wakeup_time_passed_;
absl::Mutex cancellation_mu_;
bool cancelled_ ABSL_GUARDED_BY(cancellation_mu_);
bool wakeup_called_ ABSL_GUARDED_BY(cancellation_mu_);
};
SimulatedClock::SimulatedClock(absl::Time t) : now_(t) {}
SimulatedClock::~SimulatedClock() {
// Wake up all existing waiters.
WaiterList waiters;
{
absl::MutexLock l(lock_);
waiters.swap(waiters_);
}
for (auto& iter : waiters) {
iter.second->WakeUp();
}
}
absl::Time SimulatedClock::TimeNow() {
absl::ReaderMutexLock l(lock_);
return now_;
}
void SimulatedClock::Sleep(absl::Duration d) { SleepUntil(TimeNow() + d); }
int64_t SimulatedClock::SetTime(absl::Time t) ABSL_NO_THREAD_SAFETY_ANALYSIS {
return UpdateTime([this, t]()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_) { now_ = t; });
}
int64_t SimulatedClock::AdvanceTime(absl::Duration d)
ABSL_NO_THREAD_SAFETY_ANALYSIS {
return UpdateTime([this, d]()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_) { now_ += d; });
}
template <class T>
int64_t SimulatedClock::UpdateTime(const T& now_updater) {
// Deadlock could occur if UpdateTime() were to hold lock_ while waking up
// waiters, since waking up requires acquiring the external mutex, and
// AwaitWithDeadline() acquires the mutexes in the opposite order. So let's
// first grab all the wakeup callbacks, then release lock_, then call the
// callbacks.
std::vector<WaiterList::mapped_type> wakeup_calls;
lock_.lock();
now_updater(); // reset now_
WaiterList::iterator iter;
while (((iter = waiters_.begin()) != waiters_.end()) &&
(iter->first <= now_)) {
wakeup_calls.push_back(std::move(iter->second));
waiters_.erase(iter);
}
lock_.unlock();
for (const auto& wakeup_call : wakeup_calls) {
wakeup_call->WakeUp();
}
return static_cast<int64_t>(wakeup_calls.size());
}
void SimulatedClock::SleepUntil(absl::Time wakeup_time) {
absl::Mutex mu;
absl::MutexLock lock(mu);
bool f = false;
AwaitWithDeadline(&mu, absl::Condition(&f), wakeup_time);
}
bool SimulatedClock::AwaitWithDeadline(absl::Mutex* mu,
const absl::Condition& cond,
absl::Time deadline) {
mu->AssertReaderHeld();
// Evaluate cond outside our own lock to minimize contention.
const bool ready = cond.Eval();
lock_.lock();
num_await_calls_++;
// Return now if the deadline is already past, or if the condition is true.
// This avoids creating a WakeUpInfo that won't be deleted until an
// appropriate UpdateTime() call.
if (deadline <= now_ || ready) {
lock_.unlock();
return ready;
}
auto wakeup_info = std::make_shared<WakeUpInfo>(mu, cond);
waiters_.insert(std::make_pair(deadline, wakeup_info));
lock_.unlock();
// SimulatedClock may be destroyed any time after this, so we can't
// acquire lock_ again.
// Wait until either cond.Eval() becomes true, or the deadline has passed.
wakeup_info->AwaitConditionOrWakeUp();
// Cancel the wakeup call, or if it's already in progress, wait for it to
// finish, since we must ensure no one touches 'mu' or 'cond' after we return.
wakeup_info->CancelOrAwaitWakeUp();
return cond.Eval();
}
std::optional<absl::Time> SimulatedClock::GetEarliestWakeupTime() const {
absl::ReaderMutexLock l(lock_);
if (waiters_.empty()) {
return std::nullopt;
}
return waiters_.begin()->first;
}
ABSL_NAMESPACE_END
} // namespace absl