Skip to main content

ntex_rt/
rt.rs

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