/__w/smoldot/smoldot/repo/wasm-node/rust/src/timers.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! This module provides the `Delay` struct, which implement `Future` and becomes ready after a |
19 | | //! certain time. |
20 | | //! |
21 | | //! In order to optimize performances, we avoid invoking the FFI once per timer. Instead, the FFI |
22 | | //! is only used in order to wake up when the earliest timer finishes, then restarted for the next |
23 | | //! timer. |
24 | | |
25 | | use crate::bindings; |
26 | | |
27 | | use alloc::collections::BTreeSet; |
28 | | use async_lock::Mutex; |
29 | | use core::{ |
30 | | cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, |
31 | | future, mem, |
32 | | pin::Pin, |
33 | | task::{Context, Poll, Waker}, |
34 | | time::Duration, |
35 | | }; |
36 | | |
37 | 0 | pub(crate) fn timer_finished() { |
38 | 0 | process_timers(); |
39 | 0 | } |
40 | | |
41 | | /// `Future` that automatically wakes up after a certain amount of time has elapsed. |
42 | | pub struct Delay { |
43 | | /// Index in `TIMERS::timers`. Guaranteed to have `is_obsolete` equal to `false`. |
44 | | /// If `None`, then this timer is already ready. |
45 | | timer_id: Option<usize>, |
46 | | } |
47 | | |
48 | | impl Delay { |
49 | 0 | pub fn new(after: Duration) -> Self { |
50 | 0 | let now = unsafe { Duration::from_micros(bindings::monotonic_clock_us()) }; |
51 | 0 | Self::new_inner(now + after, now) |
52 | 0 | } |
53 | | |
54 | 0 | pub fn new_at_monotonic_clock(when: Duration) -> Self { |
55 | 0 | let now = unsafe { Duration::from_micros(bindings::monotonic_clock_us()) }; |
56 | 0 | Self::new_inner(when, now) |
57 | 0 | } |
58 | | |
59 | 0 | fn new_inner(when: Duration, now: Duration) -> Self { |
60 | 0 | // Small optimization because sleeps of 0 seconds are frequent. |
61 | 0 | if when <= now { |
62 | 0 | return Delay { timer_id: None }; |
63 | 0 | } |
64 | 0 |
|
65 | 0 | // Because we're in a single-threaded environment, `try_lock()` should always succeed. |
66 | 0 | let mut lock = TIMERS.try_lock().unwrap(); |
67 | 0 |
|
68 | 0 | let timer_id = lock.timers.insert(Timer { |
69 | 0 | is_finished: false, |
70 | 0 | is_obsolete: false, |
71 | 0 | waker: None, |
72 | 0 | }); |
73 | 0 |
|
74 | 0 | lock.timers_queue.insert(QueuedTimer { when, timer_id }); |
75 | 0 |
|
76 | 0 | // If the timer that has just been inserted is the one that ends the soonest, then |
77 | 0 | // actually start the callback that will process timers. |
78 | 0 | // Ideally we would instead cancel or update the deadline of the previous call to |
79 | 0 | // `start_timer`, but this isn't possible. |
80 | 0 | if lock |
81 | 0 | .timers_queue |
82 | 0 | .first() |
83 | 0 | .unwrap_or_else(|| unreachable!()) |
84 | 0 | .timer_id |
85 | 0 | == timer_id |
86 | 0 | { |
87 | 0 | start_timer(when - now); |
88 | 0 | } |
89 | | |
90 | 0 | Delay { |
91 | 0 | timer_id: Some(timer_id), |
92 | 0 | } |
93 | 0 | } |
94 | | } |
95 | | |
96 | | impl future::Future for Delay { |
97 | | type Output = (); |
98 | | |
99 | 0 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { |
100 | 0 | let timer_id = match self.timer_id { |
101 | 0 | Some(id) => id, |
102 | 0 | None => return Poll::Ready(()), |
103 | | }; |
104 | | |
105 | | // Because we're in a single-threaded environment, `try_lock()` should always succeed. |
106 | 0 | let mut lock = TIMERS.try_lock().unwrap(); |
107 | 0 | debug_assert!(!lock.timers[timer_id].is_obsolete); |
108 | | |
109 | 0 | if lock.timers[timer_id].is_finished { |
110 | 0 | lock.timers.remove(timer_id); |
111 | 0 | self.timer_id = None; |
112 | 0 | return Poll::Ready(()); |
113 | 0 | } |
114 | 0 |
|
115 | 0 | lock.timers[timer_id].waker = Some(cx.waker().clone()); |
116 | 0 | Poll::Pending |
117 | 0 | } |
118 | | } |
119 | | |
120 | | impl Drop for Delay { |
121 | 0 | fn drop(&mut self) { |
122 | 0 | let timer_id = match self.timer_id { |
123 | 0 | Some(id) => id, |
124 | 0 | None => return, |
125 | | }; |
126 | | |
127 | | // Because we're in a single-threaded environment, `try_lock()` should always succeed. |
128 | 0 | let mut lock = TIMERS.try_lock().unwrap(); |
129 | 0 | debug_assert!(!lock.timers[timer_id].is_obsolete); |
130 | | |
131 | 0 | if lock.timers[timer_id].is_finished { |
132 | 0 | lock.timers.remove(timer_id); |
133 | 0 | return; |
134 | 0 | } |
135 | 0 |
|
136 | 0 | lock.timers[timer_id].is_obsolete = true; |
137 | 0 | lock.timers[timer_id].waker = None; |
138 | 0 | } |
139 | | } |
140 | | |
141 | | static TIMERS: Mutex<Timers> = Mutex::new(Timers { |
142 | | timers_queue: BTreeSet::new(), |
143 | | timers: slab::Slab::new(), |
144 | | }); |
145 | | |
146 | | struct Timers { |
147 | | /// Same entries as `timer`, but ordered based on when they're finished (from soonest to |
148 | | /// latest). Items are only ever removed from [`process_timers`] when they finish, even if |
149 | | /// the corresponding [`Delay`] is destroyed. |
150 | | timers_queue: BTreeSet<QueuedTimer>, |
151 | | |
152 | | /// List of all timers. |
153 | | timers: slab::Slab<Timer>, |
154 | | } |
155 | | |
156 | | struct Timer { |
157 | | /// If `true`, then this timer has elapsed. |
158 | | is_finished: bool, |
159 | | /// If `true`, then the corresponding `Delay` has been destroyed or no longer points to this |
160 | | /// item. |
161 | | is_obsolete: bool, |
162 | | /// How to wake up the `Delay`. |
163 | | waker: Option<Waker>, |
164 | | } |
165 | | |
166 | | struct QueuedTimer { |
167 | | when: Duration, |
168 | | |
169 | | // Entry in `TIMERS::timers`. Guaranteed to always have `is_finished` equal to `false`. |
170 | | timer_id: usize, |
171 | | } |
172 | | |
173 | | impl PartialEq for QueuedTimer { |
174 | 0 | fn eq(&self, other: &Self) -> bool { |
175 | 0 | matches!(self.cmp(other), Ordering::Equal) |
176 | 0 | } |
177 | | } |
178 | | |
179 | | impl Eq for QueuedTimer {} |
180 | | |
181 | | impl PartialOrd for QueuedTimer { |
182 | 0 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
183 | 0 | Some(Ord::cmp(self, other)) |
184 | 0 | } |
185 | | } |
186 | | |
187 | | impl Ord for QueuedTimer { |
188 | 0 | fn cmp(&self, other: &Self) -> Ordering { |
189 | 0 | // `when` takes higher priority in the ordering. |
190 | 0 | match self.when.cmp(&other.when) { |
191 | 0 | Ordering::Equal => self.timer_id.cmp(&other.timer_id), |
192 | 0 | ord => ord, |
193 | | } |
194 | 0 | } |
195 | | } |
196 | | |
197 | | /// Marks as ready all the timers in `TIMERS` that are finished. |
198 | 0 | fn process_timers() { |
199 | 0 | // Because we're in a single-threaded environment, `try_lock()` should always succeed. |
200 | 0 | let mut lock = TIMERS.try_lock().unwrap(); |
201 | 0 | let lock = &mut *lock; |
202 | 0 |
|
203 | 0 | let now = unsafe { Duration::from_micros(bindings::monotonic_clock_us()) }; |
204 | 0 |
|
205 | 0 | // Note that this function can be called spuriously. |
206 | 0 | // For example, `process_timers` can be scheduled twice from two different timers, and the |
207 | 0 | // first call leads to both timers being finished, after which the second call will be |
208 | 0 | // spurious. |
209 | 0 |
|
210 | 0 | // We remove all the queued timers whose `when` is inferior to `now`. |
211 | 0 | let expired_timers = { |
212 | 0 | let timers_remaining = lock.timers_queue.split_off(&QueuedTimer { |
213 | 0 | when: now, |
214 | 0 | // Note that `split_off` returns values greater or equal, meaning that if a timer had |
215 | 0 | // a `timer_id` equal to `max_value()` it would erroneously be returned instead of being |
216 | 0 | // left in the collection as expected. For obvious reasons, a `timer_id` of |
217 | 0 | // `usize::MAX` is impossible, so this isn't a problem. |
218 | 0 | timer_id: usize::MAX, |
219 | 0 | }); |
220 | 0 |
|
221 | 0 | mem::replace(&mut lock.timers_queue, timers_remaining) |
222 | | }; |
223 | | |
224 | | // Wake up the expired timers. |
225 | 0 | for timer in expired_timers { |
226 | 0 | debug_assert!(timer.when <= now); |
227 | 0 | debug_assert!(!lock.timers[timer.timer_id].is_finished); |
228 | 0 | lock.timers[timer.timer_id].is_finished = true; |
229 | 0 | if let Some(waker) = lock.timers[timer.timer_id].waker.take() { |
230 | 0 | waker.wake(); |
231 | 0 | } |
232 | | } |
233 | | |
234 | | // Figure out the next time we should call `process_timers`. |
235 | | // |
236 | | // This iterates through all the elements in `timers_queue` until a valid one is found. |
237 | 0 | let next_wakeup: Option<Duration> = loop { |
238 | 0 | let next_timer = match lock.timers_queue.first() { |
239 | 0 | Some(t) => t, |
240 | 0 | None => break None, |
241 | | }; |
242 | | |
243 | | // The `Delay` corresponding to the iterated timer has been destroyed. Removing it and |
244 | | // `continue`. |
245 | 0 | if lock.timers[next_timer.timer_id].is_obsolete { |
246 | 0 | let next_timer_id = next_timer.timer_id; |
247 | 0 | lock.timers.remove(next_timer_id); |
248 | 0 | lock.timers_queue |
249 | 0 | .pop_first() |
250 | 0 | .unwrap_or_else(|| unreachable!()); |
251 | 0 | continue; |
252 | 0 | } |
253 | 0 |
|
254 | 0 | // Iterated timer is not ready. |
255 | 0 | break Some(next_timer.when); |
256 | | }; |
257 | | |
258 | 0 | if let Some(next_wakeup) = next_wakeup { |
259 | 0 | start_timer(next_wakeup - now); |
260 | 0 | } else { |
261 | | // Clean up memory a bit. Hopefully this doesn't impact performances too much. |
262 | 0 | if !lock.timers.is_empty() && lock.timers.capacity() > lock.timers.len() * 8 { |
263 | 0 | lock.timers.shrink_to_fit(); |
264 | 0 | } |
265 | | } |
266 | 0 | } |
267 | | |
268 | | /// Instructs the environment to call [`process_timers`] after the given duration. |
269 | 0 | fn start_timer(duration: Duration) { |
270 | 0 | // Note that ideally `duration` should be rounded up in order to make sure that it is not |
271 | 0 | // truncated, but the precision of an `f64` is so high and the precision of the operating |
272 | 0 | // system generally so low that this is not worth dealing with. |
273 | 0 | unsafe { bindings::start_timer(duration.as_secs_f64() * 1000.0) } |
274 | 0 | } |