C/C++ API Reference
Loading...
Searching...
No Matches
time_provider.h
1// Copyright 2024 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15#pragma once
16
17#include <stddef.h>
18
19#include <mutex>
20
21#include "pw_async2/dispatcher.h"
22#include "pw_async2/future.h"
23#include "pw_chrono/virtual_clock.h"
24#include "pw_containers/intrusive_list.h"
25#include "pw_sync/interrupt_spin_lock.h"
26#include "pw_sync/lock_annotations.h"
27#include "pw_toolchain/no_destructor.h"
28
29namespace pw::async2 {
30
31namespace internal {
32
33// A lock which guards `TimeProvider`'s linked list.
34inline pw::sync::InterruptSpinLock& time_lock() {
36 return lock;
37}
38
39// `Timer` objects must not outlive their `TimeProvider`.
40void AssertTimeFutureObjectsAllGone(bool empty);
41
42} // namespace internal
43
45
46template <typename Clock>
47class TimeFuture;
48
62template <typename Clock>
63class TimeProvider : public chrono::VirtualClock<Clock> {
64 public:
65 ~TimeProvider() override {
66 internal::AssertTimeFutureObjectsAllGone(futures_.empty());
67 }
68
69 typename Clock::time_point now() override = 0;
70
75 [[nodiscard]] TimeFuture<Clock> WaitFor(typename Clock::duration delay) {
80 return WaitUntil(now() + delay + typename Clock::duration(1));
81 }
82
88 typename Clock::time_point timestamp) {
89 return TimeFuture<Clock>(*this, timestamp);
90 }
91
92 protected:
97 void RunExpired(typename Clock::time_point now)
98 PW_LOCKS_EXCLUDED(internal::time_lock());
99
100 private:
101 friend class TimeFuture<Clock>;
102
105 virtual void DoInvokeAt(typename Clock::time_point)
106 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
107
109 virtual void DoCancel()
110 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
111
112 // Head of the waiting timers list.
113 IntrusiveList<TimeFuture<Clock>> futures_
114 PW_GUARDED_BY(internal::time_lock());
115};
116
121template <typename Clock>
122class [[nodiscard]] TimeFuture
123 : public experimental::Future<TimeFuture<Clock>,
124 typename Clock::time_point>,
125 public IntrusiveForwardList<TimeFuture<Clock>>::Item {
126 public:
127 TimeFuture() : provider_(nullptr) {}
128 TimeFuture(const TimeFuture&) = delete;
129 TimeFuture& operator=(const TimeFuture&) = delete;
130
131 TimeFuture(TimeFuture&& other) : provider_(nullptr) {
132 *this = std::move(other);
133 }
134
135 TimeFuture& operator=(TimeFuture&& other) {
136 std::lock_guard lock(internal::time_lock());
137 UnlistLocked();
138
139 provider_ = other.provider_;
140 expiration_ = other.expiration_;
141
142 // Replace the entry of `other_` in the list.
143 if (!other.unlisted()) {
144 auto previous = provider_->futures_.before_begin();
145 while (&*std::next(previous) != &other) {
146 previous++;
147 }
148
149 // NOTE: this will leave `other` reporting (falsely) that it has expired.
150 // However, `other` should not be used post-`move`.
151 other.unlist(&*previous);
152 provider_->futures_.insert_after(previous, *this);
153 }
154
155 return *this;
156 }
157
161 ~TimeFuture() { Unlist(); }
162
163 // Returns the provider associated with this timer.
164 //
165 // NOTE: this method must not be called before initializing the timer.
167 // A lock is not required because this value is only mutated in
168 // constructors.
169 return *provider_;
170 }
171
172 // Returns the provider associated with this timer.
173 //
174 // NOTE: this method must not be called before initializing the timer.
175 // NOTE: this method must not be called with other methods that modify
176 // the expiration time such as `Reset`.
177 typename Clock::time_point expiration() PW_NO_LOCK_SAFETY_ANALYSIS {
178 // A lock is not required because this is only mutated in ``Reset`` and
179 // constructors.
180 return expiration_;
181 }
182
183 private:
184 using Base =
185 experimental::Future<TimeFuture<Clock>, typename Clock::time_point>;
186 friend Base;
187 friend class TimeProvider<Clock>;
188
189 Poll<typename Clock::time_point> DoPend(Context& cx)
190 PW_LOCKS_EXCLUDED(internal::time_lock()) {
191 std::lock_guard lock(internal::time_lock());
192 if (this->unlisted()) {
193 return Ready(expiration_);
194 }
195 // NOTE: this is done under the lock in order to ensure that `provider_` is
196 // not set to unlisted between it being initially read and `waker_` being
197 // set.
198 PW_ASYNC_STORE_WAKER(cx, waker_, "TimeFuture is waiting for a time_point");
199 return Pending();
200 }
201
202 void DoMarkComplete() {
203 std::lock_guard lock(internal::time_lock());
204 provider_ = nullptr;
205 }
206
207 // SAFETY: It is safe to read `provider_` without holding the lock.
208 bool DoIsComplete() const PW_NO_LOCK_SAFETY_ANALYSIS {
209 return provider_ == nullptr;
210 }
211
213 TimeFuture(TimeProvider<Clock>& provider,
214 typename Clock::time_point expiration)
215 : waker_(), provider_(&provider), expiration_(expiration) {
216 std::lock_guard lock(internal::time_lock());
217 EnlistLocked();
218 }
219
220 void EnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
221 // Skip enlisting if the expiration of the timer is in the past.
222 // NOTE: this *does not* trigger a waker since `Poll` has not yet been
223 // invoked, so none has been registered.
224 if (provider_->now() >= expiration_) {
225 return;
226 }
227
228 if (provider_->futures_.empty() ||
229 provider_->futures_.front().expiration_ > expiration_) {
230 provider_->futures_.push_front(*this);
231 provider_->DoInvokeAt(expiration_);
232 return;
233 }
234 auto current = provider_->futures_.begin();
235 while (std::next(current) != provider_->futures_.end() &&
236 std::next(current)->expiration_ < expiration_) {
237 current++;
238 }
239 provider_->futures_.insert_after(current, *this);
240 }
241
242 void Unlist() PW_LOCKS_EXCLUDED(internal::time_lock()) {
243 std::lock_guard lock(internal::time_lock());
244 UnlistLocked();
245 }
246
247 // Removes this timer from the `TimeProvider`'s list (if listed).
248 //
249 // If this timer was previously the `head` element of the `TimeProvider`'s
250 // list, the `TimeProvider` will be rescheduled to wake up based on the
251 // new `head`'s expiration time.
252 void UnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
253 if (this->unlisted()) {
254 return;
255 }
256 if (&provider_->futures_.front() == this) {
257 provider_->futures_.pop_front();
258 if (provider_->futures_.empty()) {
259 provider_->DoCancel();
260 } else {
261 provider_->DoInvokeAt(provider_->futures_.front().expiration_);
262 }
263 return;
264 }
265
266 provider_->futures_.remove(*this);
267 }
268
269 Waker waker_;
270 // NOTE: the lock is only required when
271 // (1) modifying these fields or
272 // (2) reading these fields via the TimeProvider.
273 //
274 // Reading these fields when nonmodification is guaranteed (such as in an
275 // accessor like ``provider`` or ``expiration`` above) does not require
276 // holding the lock.
277 TimeProvider<Clock>* provider_ PW_GUARDED_BY(internal::time_lock());
278 typename Clock::time_point expiration_ PW_GUARDED_BY(internal::time_lock());
279};
280
281template <typename Clock>
282void TimeProvider<Clock>::RunExpired(typename Clock::time_point now) {
283 std::lock_guard lock(internal::time_lock());
284 while (!futures_.empty()) {
285 if (futures_.front().expiration_ > now) {
286 DoInvokeAt(futures_.front().expiration_);
287 return;
288 }
289 std::move(futures_.front().waker_).Wake();
290 futures_.pop_front();
291 }
292}
293
295
296} // namespace pw::async2
Definition: intrusive_forward_list.h:91
Definition: time_provider.h:125
~TimeFuture()
Definition: time_provider.h:161
Definition: time_provider.h:63
virtual void DoCancel()=0
Optimistically cancels all pending DoInvokeAt requests.
Clock::time_point now() override=0
Returns the current time.
TimeFuture< Clock > WaitFor(typename Clock::duration delay)
Definition: time_provider.h:75
virtual void DoInvokeAt(typename Clock::time_point)=0
TimeFuture< Clock > WaitUntil(typename Clock::time_point timestamp)
Definition: time_provider.h:87
Definition: virtual_clock.h:31
Definition: intrusive_list.h:88
Definition: interrupt_spin_lock.h:50
constexpr PendingType Pending()
Returns a value indicating that an operation was not yet able to complete.
Definition: poll.h:271
#define PW_ASYNC_STORE_WAKER(context, waker_or_queue_out, wait_reason_string)
Definition: waker.h:60
constexpr Poll Ready()
Returns a value indicating completion.
Definition: poll.h:255
void RunExpired(typename Clock::time_point now)
Definition: time_provider.h:282
#define PW_GUARDED_BY(x)
Definition: lock_annotations.h:60
#define PW_NO_LOCK_SAFETY_ANALYSIS
Definition: lock_annotations.h:292
#define PW_EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: lock_annotations.h:146
#define PW_LOCKS_EXCLUDED(...)
Definition: lock_annotations.h:176