Coverage Report

Created: 2024-05-16 12:16

/__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
}