Skip to main content

gpu_async_reactor/core/
reactor.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! The reactor: what the tasks of one executor are waiting on (memory descriptors and timers),
4//! and the one decision the executor makes when no task is ready ([`Reactor::turn`]):
5//! 1. wake every task whose descriptor completed or whose deadline passed;
6//! 2. otherwise, with only timers pending, sleep to the earliest one (a predicted completion);
7//! 3. otherwise park on the completion line of the earliest-registered descriptor, bounded by
8//!    the park bound and the earliest deadline; any store to the line wakes the park, so every
9//!    descriptor of that line is watched by the one park.
10//!
11//! Invariants: the reactor never blocks itself (the [`Idle`] host does); a descriptor future
12//! resolves `true` only after an `Acquire` load saw its word reach the target.
13
14use alloc::vec::Vec;
15use core::cell::RefCell;
16use core::future::Future;
17use core::pin::Pin;
18use core::task::{Context, Poll, Waker};
19
20use crate::core::error::Result;
21use crate::core::line::{CompletionLine, MemoryDescriptor, reached};
22use crate::core::time::{Clock, DurNs, MonoNs, Timer};
23use crate::error;
24
25/// How an executor's thread idles when no task is ready: the host's blocking primitives. Only
26/// the executor calls these, and only between task polls.
27pub trait Idle {
28    /// Sleep until the clock reads `at` (an early return is allowed).
29    fn sleep_until(&mut self, at: MonoNs);
30
31    /// Park until a store to `line` may have made `ready(line)` true, or `bound` passes; return
32    /// `ready(line)`. An early `false` (a spurious wake) is allowed.
33    fn park_line<R: Fn(&CompletionLine) -> bool>(
34        &mut self,
35        line: &CompletionLine,
36        ready: R,
37        bound: DurNs,
38    ) -> bool;
39}
40
41struct Source<'l> {
42    id: u64,
43    desc: MemoryDescriptor<'l>,
44    until: Option<MonoNs>,
45    waker: Waker,
46}
47
48struct Alarm {
49    id: u64,
50    at: MonoNs,
51    waker: Waker,
52}
53
54#[derive(Default)]
55struct Table<'l> {
56    next_id: u64,
57    sources: Vec<Source<'l>>,
58    alarms: Vec<Alarm>,
59}
60
61/// What one [`Reactor::turn`] did.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum Turn {
64    /// At least one task was woken.
65    Woke,
66    /// The thread slept to a deadline.
67    Slept,
68    /// The thread parked on a completion line.
69    Parked,
70}
71
72/// The reactor of one executor: registrations made by the futures it hands out.
73pub struct Reactor<'l, C: Clock> {
74    clock: C,
75    table: RefCell<Table<'l>>,
76}
77
78impl<'l, C: Clock> Reactor<'l, C> {
79    /// An empty reactor reading `clock`.
80    #[must_use]
81    pub fn new(clock: C) -> Self {
82        Self {
83            clock,
84            table: RefCell::new(Table::default()),
85        }
86    }
87
88    /// The clock the reactor reads.
89    #[must_use]
90    pub fn clock(&self) -> &C {
91        &self.clock
92    }
93
94    /// A future that resolves `true` once `desc` completes, or `false` once the clock reads
95    /// `until` first (no bound with `None`).
96    #[must_use]
97    pub fn ready(&self, desc: MemoryDescriptor<'l>, until: Option<MonoNs>) -> Ready<'_, 'l, C> {
98        Ready {
99            reactor: self,
100            desc,
101            until,
102            id: None,
103        }
104    }
105
106    /// Registered descriptors and timers (for a host's counters and tests).
107    #[must_use]
108    pub fn pending(&self) -> (usize, usize) {
109        let t = self.table.borrow();
110        (t.sources.len(), t.alarms.len())
111    }
112
113    /// The executor's step when no task is ready (module docs).
114    ///
115    /// # Errors
116    /// [`Stalled`](crate::ErrorCode::Stalled) when nothing is registered: no store and no timer
117    /// can ever wake the executor.
118    pub fn turn<I: Idle>(&self, idle: &mut I, park_bound: DurNs, tasks_left: usize) -> Result<Turn> {
119        if self.fire() {
120            return Ok(Turn::Woke);
121        }
122        let now = self.clock.monotonic_ns();
123        let (deadline, line) = {
124            let t = self.table.borrow();
125            let deadline = t
126                .alarms
127                .iter()
128                .map(|a| a.at)
129                .chain(t.sources.iter().filter_map(|s| s.until))
130                .min();
131            (deadline, t.sources.first().map(|s| s.desc.line()))
132        };
133        match (line, deadline) {
134            (None, None) => Err(error!(Stalled, tasks_left)),
135            (None, Some(at)) => {
136                idle.sleep_until(at);
137                Ok(Turn::Slept)
138            }
139            (Some(line), deadline) => {
140                let bound = deadline.map_or(park_bound, |d| park_bound.min(d.since(now)));
141                let watched: Vec<_> = {
142                    let t = self.table.borrow();
143                    t.sources
144                        .iter()
145                        .filter(|s| core::ptr::eq(s.desc.line(), line))
146                        .map(|s| (s.desc.slot(), s.desc.target()))
147                        .collect()
148                };
149                idle.park_line(
150                    line,
151                    |l| watched.iter().any(|&(slot, target)| reached(l, slot, target)),
152                    bound,
153                );
154                Ok(Turn::Parked)
155            }
156        }
157    }
158
159    /// Wake every task whose descriptor completed or whose deadline passed, without idling;
160    /// `true` if any was woken. The executor calls this between task polls, so a completion
161    /// becomes ready work as soon as it lands, not only when the thread runs out of work.
162    pub fn fire(&self) -> bool {
163        let now = self.clock.monotonic_ns();
164        let due = {
165            let mut t = self.table.borrow_mut();
166            let mut due = Vec::new();
167            t.sources.retain_mut(|s| {
168                let fire = s.desc.is_complete() || s.until.is_some_and(|u| now >= u);
169                if fire {
170                    due.push(s.waker.clone());
171                }
172                !fire
173            });
174            t.alarms.retain(|a| {
175                let fire = now >= a.at;
176                if fire {
177                    due.push(a.waker.clone());
178                }
179                !fire
180            });
181            due
182        };
183        let any = !due.is_empty();
184        for w in due {
185            w.wake();
186        }
187        any
188    }
189
190    fn register_source(&self, id: &mut Option<u64>, desc: MemoryDescriptor<'l>, until: Option<MonoNs>, waker: &Waker) {
191        let mut t = self.table.borrow_mut();
192        if let Some(i) = *id
193            && let Some(s) = t.sources.iter_mut().find(|s| s.id == i)
194        {
195            s.waker.clone_from(waker);
196            return;
197        }
198        t.next_id += 1;
199        let i = t.next_id;
200        *id = Some(i);
201        t.sources.push(Source {
202            id: i,
203            desc,
204            until,
205            waker: waker.clone(),
206        });
207    }
208
209    fn register_alarm(&self, id: &mut Option<u64>, at: MonoNs, waker: &Waker) {
210        let mut t = self.table.borrow_mut();
211        if let Some(i) = *id
212            && let Some(a) = t.alarms.iter_mut().find(|a| a.id == i)
213        {
214            a.waker.clone_from(waker);
215            return;
216        }
217        t.next_id += 1;
218        let i = t.next_id;
219        *id = Some(i);
220        t.alarms.push(Alarm {
221            id: i,
222            at,
223            waker: waker.clone(),
224        });
225    }
226
227    fn forget(&self, id: Option<u64>) {
228        if let Some(i) = id {
229            let mut t = self.table.borrow_mut();
230            t.sources.retain(|s| s.id != i);
231            t.alarms.retain(|a| a.id != i);
232        }
233    }
234}
235
236/// The future of [`Reactor::ready`].
237pub struct Ready<'r, 'l, C: Clock> {
238    reactor: &'r Reactor<'l, C>,
239    desc: MemoryDescriptor<'l>,
240    until: Option<MonoNs>,
241    id: Option<u64>,
242}
243
244impl<C: Clock> Future for Ready<'_, '_, C> {
245    type Output = bool;
246
247    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<bool> {
248        let this = self.get_mut();
249        if this.desc.is_complete() {
250            this.reactor.forget(this.id.take());
251            return Poll::Ready(true);
252        }
253        if this
254            .until
255            .is_some_and(|u| this.reactor.clock.monotonic_ns() >= u)
256        {
257            this.reactor.forget(this.id.take());
258            return Poll::Ready(false);
259        }
260        this.reactor
261            .register_source(&mut this.id, this.desc, this.until, cx.waker());
262        Poll::Pending
263    }
264}
265
266impl<C: Clock> Drop for Ready<'_, '_, C> {
267    fn drop(&mut self) {
268        self.reactor.forget(self.id.take());
269    }
270}
271
272/// The reactor's sleep future (its [`Timer`]).
273pub struct Alarmed<'r, 'l, C: Clock> {
274    reactor: &'r Reactor<'l, C>,
275    at: MonoNs,
276    id: Option<u64>,
277}
278
279impl<C: Clock> Future for Alarmed<'_, '_, C> {
280    type Output = ();
281
282    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
283        let this = self.get_mut();
284        if this.reactor.clock.monotonic_ns() >= this.at {
285            this.reactor.forget(this.id.take());
286            return Poll::Ready(());
287        }
288        this.reactor.register_alarm(&mut this.id, this.at, cx.waker());
289        Poll::Pending
290    }
291}
292
293impl<C: Clock> Drop for Alarmed<'_, '_, C> {
294    fn drop(&mut self) {
295        self.reactor.forget(self.id.take());
296    }
297}
298
299impl<'l, C: Clock> Reactor<'l, C> {
300    /// A future that resolves once the clock reads `at`; the executor sleeps to the earliest one.
301    #[must_use]
302    pub fn alarm(&self, at: MonoNs) -> Alarmed<'_, 'l, C> {
303        Alarmed {
304            reactor: self,
305            at,
306            id: None,
307        }
308    }
309}
310
311impl<C: Clock> Timer for Reactor<'_, C> {
312    async fn sleep_until(&self, at: MonoNs) {
313        self.alarm(at).await;
314    }
315}
316
317impl<C: Clock> Clock for Reactor<'_, C> {
318    fn monotonic_ns(&self) -> MonoNs {
319        self.clock.monotonic_ns()
320    }
321}
322
323#[cfg(test)]
324#[path = "reactor_tests.rs"]
325pub(crate) mod tests;