Skip to main content

ntex_rt/
rt.rs

1use std::cell::{Cell, UnsafeCell};
2use std::collections::VecDeque;
3use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering};
4use std::{future::Future, io, thread};
5
6use async_task::Runnable;
7use crossbeam_queue::SegQueue;
8use swap_buffer_queue::error::{TryDequeueError, TryEnqueueError};
9use swap_buffer_queue::{Queue, buffer::ArrayBuffer};
10
11use crate::{driver::Driver, driver::Notify, driver::PollResult, handle::JoinHandle};
12
13scoped_tls::scoped_thread_local!(static CURRENT_RUNTIME: Runtime);
14
15thread_local!(static THREAD_ID: thread::ThreadId = thread::current().id());
16
17/// Returns the id of the current thread without cloning its `Thread` handle.
18fn current_thread_id() -> thread::ThreadId {
19    THREAD_ID.with(|id| *id)
20}
21
22#[derive(Debug)]
23/// The async runtime for ntex.
24///
25/// This is a thread-local runtime and cannot be sent to other threads.
26pub struct Runtime {
27    stop: Cell<bool>,
28    queue: Arc<RunnableQueue>,
29}
30
31impl Runtime {
32    /// Creates a runtime with default configuration.
33    pub fn new(handle: Box<dyn Notify>) -> Self {
34        Self::builder().build(handle)
35    }
36
37    /// Creates a runtime builder.
38    pub fn builder() -> RuntimeBuilder {
39        RuntimeBuilder::new()
40    }
41
42    #[allow(clippy::arc_with_non_send_sync)]
43    fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
44        Self {
45            stop: Cell::new(false),
46            queue: Arc::new(RunnableQueue::new(builder.event_interval, handle)),
47        }
48    }
49
50    /// Runs a closure with the runtime active on the current thread.
51    ///
52    /// ## Panics
53    ///
54    /// Panics if no runtime is active on the current thread.
55    pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
56        #[cold]
57        fn not_in_neon_runtime() -> ! {
58            panic!("not in a neon runtime")
59        }
60
61        if CURRENT_RUNTIME.is_set() {
62            CURRENT_RUNTIME.with(f)
63        } else {
64            not_in_neon_runtime()
65        }
66    }
67
68    #[inline]
69    /// Returns a handle to this runtime.
70    pub fn handle(&self) -> Handle {
71        Handle {
72            queue: self.queue.clone(),
73        }
74    }
75
76    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
77    ///
78    /// Spawning a task enables the task to execute concurrently to other tasks.
79    /// There is no guarantee that a spawned task will execute to completion.
80    pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
81        unsafe { self.spawn_unchecked(future) }
82    }
83
84    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
85    ///
86    /// # Safety
87    ///
88    /// The caller should ensure the captured lifetime is long enough.
89    pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
90        let queue = self.queue.clone();
91        let (runnable, task) = unsafe {
92            async_task::spawn_unchecked(future, move |runnable| {
93                queue.schedule(runnable);
94            })
95        };
96        runnable.schedule();
97        JoinHandle::new(task)
98    }
99
100    /// Marks the runtime as awake.
101    ///
102    /// Drivers call this after returning from a blocking wait, before handling
103    /// events that wake tasks. Tasks scheduled from the runtime thread do not
104    /// notify the driver until [`poll`](Self::poll) finds no more tasks to run,
105    /// the driver must call `poll` before blocking again.
106    #[inline]
107    pub fn awake(&self) {
108        self.queue.idle.set(false);
109    }
110
111    /// Polls the runtime and runs scheduled tasks.
112    pub fn poll(&self) -> PollResult {
113        if self.stop.get() {
114            PollResult::Ready
115        } else if self.queue.run() {
116            PollResult::PollAgain
117        } else {
118            PollResult::Pending
119        }
120    }
121
122    /// Runs the provided future.
123    ///
124    /// Blocks the current thread until the future completes.
125    ///
126    /// # Panics
127    ///
128    /// Panics if the driver fails to run the provided future.
129    pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
130        self.stop.set(false);
131
132        CURRENT_RUNTIME.set(self, || {
133            let mut result = None;
134            unsafe {
135                self.spawn_unchecked(async {
136                    result = Some(future.await);
137                    self.stop.set(true);
138                    let _ = self.queue.handle.notify();
139                });
140            }
141
142            ntex_error::set_backtrace_start_alt("src/raw.rs", 0);
143            driver.run(self).expect("Driver failed");
144            result.expect("Driver failed to poll")
145        })
146    }
147}
148
149impl Drop for Runtime {
150    fn drop(&mut self) {
151        CURRENT_RUNTIME.set(self, || {
152            self.queue.clear();
153        });
154    }
155}
156
157#[derive(Debug)]
158/// A thread-safe handle used to schedule work on a runtime.
159pub struct Handle {
160    queue: Arc<RunnableQueue>,
161}
162
163impl Handle {
164    /// Returns a handle to the runtime active on the current thread.
165    ///
166    /// # Panics
167    ///
168    /// Panics if no runtime is active on the current thread.
169    pub fn current() -> Handle {
170        Runtime::with_current(Runtime::handle)
171    }
172
173    /// Wakes the runtime's driver.
174    pub fn notify(&self) -> io::Result<()> {
175        self.queue.handle.notify()
176    }
177
178    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
179    ///
180    /// Spawning a task enables the task to execute concurrently to other tasks.
181    /// There is no guarantee that a spawned task will execute to completion.
182    pub fn spawn<F: Future + Send + 'static>(&self, future: F) -> JoinHandle<F::Output> {
183        let queue = self.queue.clone();
184        let schedule = move |runnable| {
185            queue.schedule(runnable);
186        };
187        let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
188        runnable.schedule();
189        JoinHandle::new(task)
190    }
191}
192
193impl Clone for Handle {
194    fn clone(&self) -> Self {
195        Self {
196            queue: self.queue.clone(),
197        }
198    }
199}
200
201#[derive(Debug)]
202struct RunnableQueue {
203    id: thread::ThreadId,
204    idle: Cell<bool>,
205    // set by the first remote `schedule` after `run` cleared it
206    notified: AtomicBool,
207    handle: Box<dyn Notify>,
208    event_interval: usize,
209    local_queue: UnsafeCell<VecDeque<Runnable>>,
210    sync_fixed_queue: Queue<ArrayBuffer<Runnable, 128>>,
211    sync_queue: SegQueue<Runnable>,
212}
213
214unsafe impl Send for RunnableQueue {}
215unsafe impl Sync for RunnableQueue {}
216
217impl RunnableQueue {
218    fn new(event_interval: usize, handle: Box<dyn Notify>) -> Self {
219        Self {
220            handle,
221            event_interval,
222            id: current_thread_id(),
223            idle: Cell::new(true),
224            notified: AtomicBool::new(false),
225            local_queue: UnsafeCell::new(VecDeque::new()),
226            sync_fixed_queue: Queue::default(),
227            sync_queue: SegQueue::new(),
228        }
229    }
230
231    fn schedule(&self, runnable: Runnable) {
232        if self.id == current_thread_id() {
233            unsafe { (*self.local_queue.get()).push_back(runnable) };
234            if self.idle.get() {
235                self.idle.set(false);
236                self.handle.notify().ok();
237            }
238        } else {
239            let result = self.sync_fixed_queue.try_enqueue([runnable]);
240            if let Err(TryEnqueueError::InsufficientCapacity([runnable])) = result {
241                self.sync_queue.push(runnable);
242            }
243            // the driver is already notified, `run` drains this task after
244            // clearing the flag
245            if !self.notified.swap(true, Ordering::Release) {
246                self.handle.notify().ok();
247            }
248        }
249    }
250
251    fn run(&self) -> bool {
252        // cleared before draining the sync queues, a task enqueued after the
253        // drain notifies the driver again
254        if self.notified.load(Ordering::Relaxed) {
255            self.notified.swap(false, Ordering::Acquire);
256        }
257
258        // a running task may schedule into `local_queue`, so it must not be
259        // borrowed across `task.run()`
260        for _ in 0..self.event_interval {
261            if let Some(task) = self.pop_local() {
262                task.run();
263            } else {
264                break;
265            }
266        }
267
268        let sync_queue_fixed = match self.sync_fixed_queue.try_dequeue() {
269            Ok(buf) => {
270                for task in buf {
271                    task.run();
272                }
273                false
274            }
275            Err(TryDequeueError::Empty | TryDequeueError::Closed) => false,
276            Err(_) => true,
277        };
278
279        let sync_queue = {
280            for _ in 0..self.event_interval {
281                if let Some(task) = self.sync_queue.pop() {
282                    task.run();
283                } else {
284                    break;
285                }
286            }
287            !self.sync_queue.is_empty()
288        };
289
290        // tasks from other threads may schedule local tasks
291        let local_queue = unsafe { !(*self.local_queue.get()).is_empty() };
292        let more_tasks = local_queue || sync_queue_fixed || sync_queue;
293        if !more_tasks {
294            self.idle.set(true);
295        }
296        more_tasks
297    }
298
299    fn clear(&self) {
300        while self.sync_queue.pop().is_some() {}
301        while self.sync_fixed_queue.try_dequeue().is_ok() {}
302        // dropped tasks may schedule other tasks, drop each outside of the borrow
303        while let Some(task) = self.pop_local() {
304            drop(task);
305        }
306    }
307
308    fn pop_local(&self) -> Option<Runnable> {
309        unsafe { (*self.local_queue.get()).pop_front() }
310    }
311}
312
313/// Builder for [`Runtime`].
314#[derive(Debug, Clone)]
315pub struct RuntimeBuilder {
316    event_interval: usize,
317}
318
319impl Default for RuntimeBuilder {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325impl RuntimeBuilder {
326    /// Create the builder with default config.
327    pub fn new() -> Self {
328        Self { event_interval: 61 }
329    }
330
331    /// Sets the number of scheduler ticks after which the scheduler will poll
332    /// for external events (timers, I/O, and so on).
333    ///
334    /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
335    /// Values below 1 are treated as 1.
336    pub fn event_interval(&mut self, val: usize) -> &mut Self {
337        self.event_interval = val.max(1);
338        self
339    }
340
341    /// Build [`Runtime`].
342    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
343        Runtime::with_builder(self, handle)
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use std::task::{Poll, Waker};
350    use std::{cell::RefCell, future::poll_fn, rc::Rc};
351
352    use super::*;
353
354    #[derive(Debug)]
355    struct NoopNotify;
356
357    impl Notify for NoopNotify {
358        fn notify(&self) -> io::Result<()> {
359            Ok(())
360        }
361    }
362
363    struct WakeOnDrop(Rc<RefCell<Option<Waker>>>);
364
365    impl Drop for WakeOnDrop {
366        fn drop(&mut self) {
367            if let Some(w) = self.0.borrow_mut().take() {
368                w.wake();
369            }
370        }
371    }
372
373    #[derive(Debug, Default)]
374    struct CountNotify(Arc<std::sync::atomic::AtomicUsize>);
375
376    impl Notify for CountNotify {
377        fn notify(&self) -> io::Result<()> {
378            self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
379            Ok(())
380        }
381    }
382
383    #[test]
384    fn awake_skips_local_notify() {
385        use std::sync::atomic::Ordering;
386
387        let cnt = Arc::new(std::sync::atomic::AtomicUsize::new(0));
388        let rt = Runtime::new(Box::new(CountNotify(cnt.clone())));
389
390        // the driver is awake and polls the runtime before blocking
391        rt.awake();
392        rt.spawn(async {}).detach();
393        assert_eq!(cnt.load(Ordering::Relaxed), 0);
394        assert_eq!(rt.poll(), PollResult::Pending);
395
396        // the runtime is idle, the driver may block
397        rt.spawn(async {}).detach();
398        rt.spawn(async {}).detach();
399        assert_eq!(cnt.load(Ordering::Relaxed), 1);
400        assert_eq!(rt.poll(), PollResult::Pending);
401    }
402
403    #[test]
404    fn remote_schedule_notifies_once() {
405        let cnt = Arc::new(std::sync::atomic::AtomicUsize::new(0));
406        let rt = Runtime::new(Box::new(CountNotify(cnt.clone())));
407        let spawn = |n| {
408            let hnd = rt.handle();
409            std::thread::spawn(move || {
410                for _ in 0..n {
411                    hnd.spawn(async {}).detach();
412                }
413            })
414            .join()
415            .unwrap();
416        };
417
418        spawn(100);
419        assert_eq!(cnt.load(Ordering::Relaxed), 1);
420        assert_eq!(rt.poll(), PollResult::Pending);
421
422        // `poll` clears the flag
423        spawn(100);
424        assert_eq!(cnt.load(Ordering::Relaxed), 2);
425        assert_eq!(rt.poll(), PollResult::Pending);
426    }
427
428    #[test]
429    fn remote_schedule_no_lost_wakeup() {
430        use std::sync::{Condvar, Mutex, atomic::AtomicUsize};
431        use std::time::Duration;
432
433        #[derive(Debug, Default)]
434        struct Event(Mutex<bool>, Condvar);
435
436        #[derive(Debug)]
437        struct EventNotify(Arc<Event>);
438
439        impl Notify for EventNotify {
440            fn notify(&self) -> io::Result<()> {
441                *self.0.0.lock().unwrap() = true;
442                self.0.1.notify_one();
443                Ok(())
444            }
445        }
446
447        const THREADS: usize = 4;
448        const TASKS: usize = 10_000;
449
450        let ev = Arc::new(Event::default());
451        let rt = Runtime::new(Box::new(EventNotify(ev.clone())));
452        let cnt = Arc::new(AtomicUsize::new(0));
453        let threads: Vec<_> = (0..THREADS)
454            .map(|_| {
455                let (hnd, cnt) = (rt.handle(), cnt.clone());
456                std::thread::spawn(move || {
457                    for _ in 0..TASKS {
458                        let cnt = cnt.clone();
459                        hnd.spawn(async move {
460                            cnt.fetch_add(1, Ordering::Relaxed);
461                        })
462                        .detach();
463                    }
464                })
465            })
466            .collect();
467
468        // blocks like a driver, a lost wakeup times out
469        while cnt.load(Ordering::Relaxed) < THREADS * TASKS {
470            if rt.poll() == PollResult::Pending {
471                let guard = ev.0.lock().unwrap();
472                let (mut guard, res) =
473                    ev.1.wait_timeout_while(guard, Duration::from_secs(5), |n| !*n)
474                        .unwrap();
475                assert!(!res.timed_out(), "lost wakeup");
476                *guard = false;
477            }
478        }
479        for t in threads {
480            t.join().unwrap();
481        }
482    }
483
484    #[test]
485    fn local_task_woken_by_remote_task() {
486        use std::sync::{Mutex, atomic::AtomicBool, atomic::Ordering};
487
488        let rt = Runtime::new(Box::new(NoopNotify));
489        let waker = Arc::new(Mutex::new(None::<Waker>));
490        let done = Rc::new(AtomicBool::new(false));
491        let (waker2, done2) = (waker.clone(), done.clone());
492        rt.spawn(poll_fn(move |cx| {
493            if waker2.lock().unwrap().replace(cx.waker().clone()).is_some() {
494                done2.store(true, Ordering::Relaxed);
495                Poll::Ready(())
496            } else {
497                Poll::Pending
498            }
499        }))
500        .detach();
501        assert_eq!(rt.poll(), PollResult::Pending);
502
503        let hnd = rt.handle();
504        std::thread::spawn(move || {
505            hnd.spawn(async move {
506                waker.lock().unwrap().clone().unwrap().wake();
507            })
508            .detach();
509        })
510        .join()
511        .unwrap();
512
513        // the remote task schedules the local task, it must be polled again
514        assert_eq!(rt.poll(), PollResult::PollAgain);
515        assert_eq!(rt.poll(), PollResult::Pending);
516        assert!(done.load(Ordering::Relaxed));
517    }
518
519    #[test]
520    fn schedule_while_running() {
521        let rt = Runtime::new(Box::new(NoopNotify));
522        let done = Rc::new(RefCell::new(0));
523        let done2 = done.clone();
524        rt.spawn(async move {
525            // schedules into the local queue from a running task
526            let h = Runtime::with_current(|rt| rt.spawn(async { 1 }));
527            *done2.borrow_mut() = h.await.unwrap();
528        })
529        .detach();
530        CURRENT_RUNTIME.set(&rt, || while rt.poll() == PollResult::PollAgain {});
531        assert_eq!(*done.borrow(), 1);
532    }
533
534    #[test]
535    fn event_interval() {
536        use std::sync::atomic::{AtomicUsize, Ordering};
537
538        for val in [0, 1] {
539            let rt = Runtime::builder()
540                .event_interval(val)
541                .build(Box::new(NoopNotify));
542            assert_eq!(rt.poll(), PollResult::Pending);
543
544            rt.spawn(async {}).detach();
545            rt.spawn(async {}).detach();
546            assert_eq!(rt.poll(), PollResult::PollAgain);
547            assert_eq!(rt.poll(), PollResult::Pending);
548        }
549
550        // tasks scheduled from other threads overflow the fixed queue
551        let rt = Runtime::builder()
552            .event_interval(1)
553            .build(Box::new(NoopNotify));
554        let cnt = Arc::new(AtomicUsize::new(0));
555        let hnd = rt.handle();
556        let cnt2 = cnt.clone();
557        std::thread::spawn(move || {
558            for _ in 0..130 {
559                let cnt = cnt2.clone();
560                hnd.spawn(async move {
561                    cnt.fetch_add(1, Ordering::Relaxed);
562                })
563                .detach();
564            }
565        })
566        .join()
567        .unwrap();
568        assert_eq!(rt.poll(), PollResult::PollAgain);
569        assert_eq!(cnt.load(Ordering::Relaxed), 129);
570        assert_eq!(rt.poll(), PollResult::Pending);
571        assert_eq!(cnt.load(Ordering::Relaxed), 130);
572    }
573
574    #[test]
575    fn schedule_while_clearing() {
576        let rt = Runtime::new(Box::new(NoopNotify));
577        let waker = Rc::new(RefCell::new(None));
578        let waker2 = waker.clone();
579        rt.spawn(poll_fn(move |cx| {
580            *waker2.borrow_mut() = Some(cx.waker().clone());
581            Poll::<()>::Pending
582        }))
583        .detach();
584        assert_eq!(rt.poll(), PollResult::Pending);
585        assert!(waker.borrow().is_some());
586
587        // queued task wakes the waiting task while the queue is cleared
588        let guard = WakeOnDrop(waker.clone());
589        rt.spawn(async move {
590            let _g = guard;
591        })
592        .detach();
593        rt.spawn(async {}).detach();
594        drop(rt);
595        assert!(waker.borrow().is_none());
596        // the woken task is dropped too, releasing its clone of `waker`
597        assert_eq!(Rc::strong_count(&waker), 1);
598    }
599}