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_chrono/virtual_clock.h"
23#include "pw_containers/intrusive_list.h"
24#include "pw_sync/interrupt_spin_lock.h"
25#include "pw_sync/lock_annotations.h"
26#include "pw_toolchain/no_destructor.h"
27
28namespace pw::async2 {
29
30namespace internal {
31
32// A lock which guards `TimeProvider`'s linked list.
33inline pw::sync::InterruptSpinLock& time_lock() {
35 return lock;
36}
37
38// `Timer` objects must not outlive their `TimeProvider`.
39void AssertTimeFutureObjectsAllGone(bool empty);
40
41} // namespace internal
42
44
45template <typename Clock>
46class TimeFuture;
47
61template <typename Clock>
62class TimeProvider : public chrono::VirtualClock<Clock> {
63 public:
64 ~TimeProvider() override {
65 internal::AssertTimeFutureObjectsAllGone(futures_.empty());
66 }
67
68 typename Clock::time_point now() override = 0;
69
74 [[nodiscard]] TimeFuture<Clock> WaitFor(typename Clock::duration delay) {
79 return WaitUntil(now() + delay + typename Clock::duration(1));
80 }
81
87 typename Clock::time_point timestamp) {
88 return TimeFuture<Clock>(*this, timestamp);
89 }
90
91 protected:
96 void RunExpired(typename Clock::time_point now)
97 PW_LOCKS_EXCLUDED(internal::time_lock());
98
99 private:
100 friend class TimeFuture<Clock>;
101
104 virtual void DoInvokeAt(typename Clock::time_point)
105 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
106
108 virtual void DoCancel()
109 PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) = 0;
110
111 // Head of the waiting timers list.
112 IntrusiveList<TimeFuture<Clock>> futures_
113 PW_GUARDED_BY(internal::time_lock());
114};
115
120template <typename Clock>
121class [[nodiscard]] TimeFuture
122 : public IntrusiveForwardList<TimeFuture<Clock>>::Item {
123 public:
124 TimeFuture() : provider_(nullptr) {}
125 TimeFuture(const TimeFuture&) = delete;
126 TimeFuture& operator=(const TimeFuture&) = delete;
127
128 TimeFuture(TimeFuture&& other) : provider_(nullptr) {
129 *this = std::move(other);
130 }
131
132 TimeFuture& operator=(TimeFuture&& other) {
133 std::lock_guard lock(internal::time_lock());
134 UnlistLocked();
135
136 provider_ = other.provider_;
137 expiration_ = other.expiration_;
138
139 // Replace the entry of `other_` in the list.
140 if (!other.unlisted()) {
141 auto previous = provider_->futures_.before_begin();
142 while (&*std::next(previous) != &other) {
143 previous++;
144 }
145
146 // NOTE: this will leave `other` reporting (falsely) that it has expired.
147 // However, `other` should not be used post-`move`.
148 other.unlist(&*previous);
149 provider_->futures_.insert_after(previous, *this);
150 }
151
152 return *this;
153 }
154
158 ~TimeFuture() { Unlist(); }
159
161 PW_LOCKS_EXCLUDED(internal::time_lock()) {
162 std::lock_guard lock(internal::time_lock());
163 if (this->unlisted()) {
164 return Ready(expiration_);
165 }
166 // NOTE: this is done under the lock in order to ensure that `provider_` is
167 // not set to unlisted between it being initially read and `waker_` being
168 // set.
169 PW_ASYNC_STORE_WAKER(cx, waker_, "TimeFuture is waiting for a time_point");
170 return Pending();
171 }
172
174 void Reset(typename Clock::time_point expiration)
175 PW_LOCKS_EXCLUDED(internal::time_lock()) {
176 std::lock_guard lock(internal::time_lock());
177 UnlistLocked();
178 expiration_ = expiration;
179 EnlistLocked();
180 }
181
182 // Returns the provider associated with this timer.
183 //
184 // NOTE: this method must not be called before initializing the timer.
186 // A lock is not required because this value is only mutated in
187 // constructors.
188 return *provider_;
189 }
190
191 // Returns the provider associated with this timer.
192 //
193 // NOTE: this method must not be called before initializing the timer.
194 // NOTE: this method must not be called with other methods that modify
195 // the expiration time such as `Reset`.
196 typename Clock::time_point expiration() PW_NO_LOCK_SAFETY_ANALYSIS {
197 // A lock is not required because this is only mutated in ``Reset`` and
198 // constructors.
199 return expiration_;
200 }
201
202 private:
203 friend class TimeProvider<Clock>;
204
206 TimeFuture(TimeProvider<Clock>& provider,
207 typename Clock::time_point expiration)
208 : waker_(), provider_(&provider), expiration_(expiration) {
209 std::lock_guard lock(internal::time_lock());
210 EnlistLocked();
211 }
212
213 void EnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
214 // Skip enlisting if the expiration of the timer is in the past.
215 // NOTE: this *does not* trigger a waker since `Poll` has not yet been
216 // invoked, so none has been registered.
217 if (provider_->now() >= expiration_) {
218 return;
219 }
220
221 if (provider_->futures_.empty() ||
222 provider_->futures_.front().expiration_ > expiration_) {
223 provider_->futures_.push_front(*this);
224 provider_->DoInvokeAt(expiration_);
225 return;
226 }
227 auto current = provider_->futures_.begin();
228 while (std::next(current) != provider_->futures_.end() &&
229 std::next(current)->expiration_ < expiration_) {
230 current++;
231 }
232 provider_->futures_.insert_after(current, *this);
233 }
234
235 void Unlist() PW_LOCKS_EXCLUDED(internal::time_lock()) {
236 std::lock_guard lock(internal::time_lock());
237 UnlistLocked();
238 }
239
240 // Removes this timer from the `TimeProvider`'s list (if listed).
241 //
242 // If this timer was previously the `head` element of the `TimeProvider`'s
243 // list, the `TimeProvider` will be rescheduled to wake up based on the
244 // new `head`'s expiration time.
245 void UnlistLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(internal::time_lock()) {
246 if (this->unlisted()) {
247 return;
248 }
249 if (&provider_->futures_.front() == this) {
250 provider_->futures_.pop_front();
251 if (provider_->futures_.empty()) {
252 provider_->DoCancel();
253 } else {
254 provider_->DoInvokeAt(provider_->futures_.front().expiration_);
255 }
256 return;
257 }
258
259 provider_->futures_.remove(*this);
260 }
261
262 Waker waker_;
263 // NOTE: the lock is only required when
264 // (1) modifying these fields or
265 // (2) reading these fields via the TimeProvider.
266 //
267 // Reading these fields when nonmodification is guaranteed (such as in an
268 // accessor like ``provider`` or ``expiration`` above) does not require
269 // holding the lock.
270 TimeProvider<Clock>* provider_ PW_GUARDED_BY(internal::time_lock());
271 typename Clock::time_point expiration_ PW_GUARDED_BY(internal::time_lock());
272};
273
274template <typename Clock>
275void TimeProvider<Clock>::RunExpired(typename Clock::time_point now) {
276 std::lock_guard lock(internal::time_lock());
277 while (!futures_.empty()) {
278 if (futures_.front().expiration_ > now) {
279 DoInvokeAt(futures_.front().expiration_);
280 return;
281 }
282 std::move(futures_.front().waker_).Wake();
283 futures_.pop_front();
284 }
285}
286
288
289} // namespace pw::async2
Definition: intrusive_forward_list.h:91
Definition: context.h:55
Definition: poll.h:60
Definition: time_provider.h:122
~TimeFuture()
Definition: time_provider.h:158
void Reset(typename Clock::time_point expiration)
Resets TimeFuture to expire at expiration.
Definition: time_provider.h:174
Definition: time_provider.h:62
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:74
virtual void DoInvokeAt(typename Clock::time_point)=0
TimeFuture< Clock > WaitUntil(typename Clock::time_point timestamp)
Definition: time_provider.h:86
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:275
#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