Skip to main content

gpu_async_reactor/host/
std_host.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! std hosts:
4//! - [`StdClock`]: `Instant` from an origin, as the core's [`Clock`]; its [`Timer`] sleeps the
5//!   calling thread (an inline host: the future is ready on its first poll).
6//! - [`StdIdle`]: the executor's idle: `thread::sleep` to a deadline; a park on a line through
7//!   the `wordwait` backend when one was probed, else a yield loop to the bound.
8//! - [`Watcher`]: a background thread that waits on completion lines and wakes the [`Waker`]s
9//!   of futures awaiting them, for consumers on any runtime that are not latency-critical. Each
10//!   [`Watch`] owns one registration, removed when it resolves or is dropped; wakers run with no
11//!   lock held.
12
13use std::sync::{Arc, Mutex, PoisonError};
14use std::task::{Context, Poll, Waker};
15use std::thread::{self, JoinHandle};
16use std::time::{Duration, Instant};
17use std::vec::Vec;
18
19use crate::core::line::{CompletionLine, Slot, reached};
20use crate::core::reactor::Idle;
21use crate::core::time::{Clock, DurNs, MonoNs, Timer};
22
23/// The monotonic clock of the process, from an origin.
24#[derive(Clone, Copy, Debug)]
25pub struct StdClock {
26    origin: Instant,
27}
28
29impl StdClock {
30    /// A clock whose 0 is now.
31    #[must_use]
32    pub fn new() -> Self {
33        Self {
34            origin: Instant::now(),
35        }
36    }
37
38    /// A clock whose 0 is `origin`.
39    #[must_use]
40    pub fn from_origin(origin: Instant) -> Self {
41        Self { origin }
42    }
43}
44
45impl Default for StdClock {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl Clock for StdClock {
52    fn monotonic_ns(&self) -> MonoNs {
53        MonoNs(u64::try_from(self.origin.elapsed().as_nanos()).unwrap_or(u64::MAX))
54    }
55}
56
57impl Timer for StdClock {
58    async fn sleep_until(&self, at: MonoNs) {
59        let left = at.since(self.monotonic_ns());
60        if left.0 > 0 {
61            thread::sleep(Duration::from_nanos(left.0));
62        }
63    }
64}
65
66/// How a [`StdIdle`] parks on a line.
67#[derive(Clone, Copy, Debug)]
68enum LinePark {
69    #[cfg(feature = "wordwait")]
70    Word(crate::backend::wordwait::WordWait),
71    Yield,
72}
73
74/// The executor's idle on a std thread.
75#[derive(Clone, Copy, Debug)]
76pub struct StdIdle {
77    clock: StdClock,
78    park: LinePark,
79}
80
81impl StdIdle {
82    /// An idle that parks through the CPU's word wait when the `wordwait` feature is on and the
83    /// CPU has one; otherwise it yields to the bound (the declared fallback, see
84    /// [`StdIdle::parks_in_hardware`]).
85    #[must_use]
86    pub fn new(clock: StdClock) -> Self {
87        #[cfg(feature = "wordwait")]
88        if let Ok(w) = crate::backend::wordwait::WordWait::probe() {
89            return Self {
90                clock,
91                park: LinePark::Word(w),
92            };
93        }
94        Self {
95            clock,
96            park: LinePark::Yield,
97        }
98    }
99
100    /// An idle that parks through `wait` (a word wait probed by the caller).
101    #[cfg(feature = "wordwait")]
102    #[must_use]
103    pub fn with_word_wait(clock: StdClock, wait: crate::backend::wordwait::WordWait) -> Self {
104        Self {
105            clock,
106            park: LinePark::Word(wait),
107        }
108    }
109
110    /// Whether a park sleeps the core in hardware (`false`: it yields the thread in a loop).
111    #[must_use]
112    pub fn parks_in_hardware(&self) -> bool {
113        !matches!(self.park, LinePark::Yield)
114    }
115}
116
117impl Idle for StdIdle {
118    fn sleep_until(&mut self, at: MonoNs) {
119        let left = at.since(self.clock.monotonic_ns());
120        if left.0 > 0 {
121            thread::sleep(Duration::from_nanos(left.0));
122        }
123    }
124
125    fn park_line<R: Fn(&CompletionLine) -> bool>(
126        &mut self,
127        line: &CompletionLine,
128        ready: R,
129        bound: DurNs,
130    ) -> bool {
131        match self.park {
132            #[cfg(feature = "wordwait")]
133            LinePark::Word(w) => w.park(line, ready, Duration::from_nanos(bound.0)),
134            LinePark::Yield => {
135                let until = Instant::now() + Duration::from_nanos(bound.0);
136                while !ready(line) {
137                    if Instant::now() >= until {
138                        return false;
139                    }
140                    thread::yield_now();
141                }
142                true
143            }
144        }
145    }
146}
147
148fn line_of<L: AsRef<CompletionLine>>(l: &Arc<L>) -> &CompletionLine {
149    (**l).as_ref()
150}
151
152/// One [`Watch`]'s registration.
153struct Entry<L> {
154    id: u64,
155    line: Arc<L>,
156    slot: Slot,
157    target: u64,
158    waker: Waker,
159}
160
161/// The registrations, and the id the next one gets.
162struct Table<L> {
163    next: u64,
164    entries: Vec<Entry<L>>,
165}
166
167struct Shared<L> {
168    table: Mutex<Table<L>>,
169    stop: std::sync::atomic::AtomicBool,
170}
171
172impl<L> Shared<L> {
173    fn lock(&self) -> std::sync::MutexGuard<'_, Table<L>> {
174        self.table.lock().unwrap_or_else(PoisonError::into_inner)
175    }
176}
177
178/// A thread that watches completion lines and wakes the futures awaiting them. `L` is anything
179/// that holds a line (a backend's mapped line, an `Arc`'d plain line).
180pub struct Watcher<L: AsRef<CompletionLine> + Send + Sync + 'static> {
181    shared: Arc<Shared<L>>,
182    thread: Option<JoinHandle<()>>,
183}
184
185impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Watcher<L> {
186    /// Start the watcher thread; it parks on a watched line for at most `park_bound` at a time
187    /// and sleeps (thread park) while nothing is watched.
188    #[must_use]
189    pub fn spawn(mut idle: StdIdle, park_bound: Duration) -> Self {
190        let shared = Arc::new(Shared {
191            table: Mutex::new(Table {
192                next: 0,
193                entries: Vec::new(),
194            }),
195            stop: std::sync::atomic::AtomicBool::new(false),
196        });
197        let s = Arc::clone(&shared);
198        let bound = DurNs(u64::try_from(park_bound.as_nanos()).unwrap_or(u64::MAX));
199        let thread = thread::spawn(move || {
200            while !s.stop.load(std::sync::atomic::Ordering::Acquire) {
201                let (done, first) = {
202                    let mut t = s.lock();
203                    let done: Vec<Entry<L>> = t
204                        .entries
205                        .extract_if(.., |x| reached(line_of(&x.line), x.slot, x.target))
206                        .collect();
207                    let first = t.entries.first().map(|x| (Arc::clone(&x.line), x.slot, x.target));
208                    (done, first)
209                };
210                // Woken with the lock released: a waker may poll or drop a `Watch` of this
211                // watcher (an inline executor does), which takes the lock.
212                for e in done {
213                    e.waker.wake();
214                }
215                match first {
216                    None => thread::park(),
217                    Some((line, slot, target)) => {
218                        let _ = idle.park_line(
219                            line_of(&line),
220                            |l| reached(l, slot, target),
221                            bound,
222                        );
223                    }
224                }
225            }
226        });
227        Self {
228            shared,
229            thread: Some(thread),
230        }
231    }
232
233    /// A future that resolves once word `slot` of `line` reaches `target`.
234    #[must_use]
235    pub fn wait(&self, line: Arc<L>, slot: Slot, target: u64) -> Watch<L> {
236        Watch {
237            shared: Arc::clone(&self.shared),
238            thread: self.thread.as_ref().map(|t| t.thread().clone()),
239            line,
240            slot,
241            target,
242            id: None,
243        }
244    }
245}
246
247impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Drop for Watcher<L> {
248    fn drop(&mut self) {
249        self.shared
250            .stop
251            .store(true, std::sync::atomic::Ordering::Release);
252        if let Some(t) = self.thread.take() {
253            t.thread().unpark();
254            let _ = t.join();
255        }
256    }
257}
258
259/// The future of [`Watcher::wait`]: `Send`, usable on any runtime. Dropping it before it
260/// resolves (a cancelled await) removes its registration.
261pub struct Watch<L: AsRef<CompletionLine> + Send + Sync + 'static> {
262    shared: Arc<Shared<L>>,
263    thread: Option<thread::Thread>,
264    line: Arc<L>,
265    slot: Slot,
266    target: u64,
267    /// This future's registration in the watcher, once polled pending.
268    id: Option<u64>,
269}
270
271impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Watch<L> {
272    fn reached(&self) -> bool {
273        reached(line_of(&self.line), self.slot, self.target)
274    }
275
276    /// Remove this future's registration, if it still has one. The entry (its waker and line) is
277    /// dropped after the lock is released.
278    fn deregister(&mut self) {
279        let Some(id) = self.id.take() else { return };
280        let removed = {
281            let mut t = self.shared.lock();
282            t.entries
283                .iter()
284                .position(|x| x.id == id)
285                .map(|i| t.entries.swap_remove(i))
286        };
287        drop(removed);
288    }
289}
290
291impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Future for Watch<L> {
292    type Output = ();
293
294    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
295        let this = self.get_mut();
296        if this.reached() {
297            this.deregister();
298            return Poll::Ready(());
299        }
300        {
301            let mut t = this.shared.lock();
302            let mine = this.id.and_then(|id| t.entries.iter_mut().find(|x| x.id == id));
303            match mine {
304                Some(x) => x.waker.clone_from(cx.waker()),
305                // Not registered yet, or the watcher took the entry out to wake it.
306                None => {
307                    let id = t.next;
308                    t.next = t.next.wrapping_add(1);
309                    t.entries.push(Entry {
310                        id,
311                        line: Arc::clone(&this.line),
312                        slot: this.slot,
313                        target: this.target,
314                        waker: cx.waker().clone(),
315                    });
316                    this.id = Some(id);
317                }
318            }
319        }
320        if let Some(t) = &this.thread {
321            t.unpark();
322        }
323        // A store between the first check and the registration is seen by the watcher's next
324        // pass; checking again here makes it immediate.
325        if this.reached() {
326            this.deregister();
327            return Poll::Ready(());
328        }
329        Poll::Pending
330    }
331}
332
333impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Drop for Watch<L> {
334    fn drop(&mut self) {
335        self.deregister();
336    }
337}