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    /// Polls the runtime and runs scheduled tasks.
100    pub fn poll(&self) -> PollResult {
101        if self.stop.get() {
102            PollResult::Ready
103        } else if self.queue.run() {
104            PollResult::PollAgain
105        } else {
106            PollResult::Pending
107        }
108    }
109
110    /// Runs the provided future.
111    ///
112    /// Blocks the current thread until the future completes.
113    ///
114    /// # Panics
115    ///
116    /// Panics if the driver fails to run the provided future.
117    pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
118        self.stop.set(false);
119
120        CURRENT_RUNTIME.set(self, || {
121            let mut result = None;
122            unsafe {
123                self.spawn_unchecked(async {
124                    result = Some(future.await);
125                    self.stop.set(true);
126                    let _ = self.queue.handle.notify();
127                });
128            }
129
130            ntex_error::set_backtrace_start_alt("src/raw.rs", 0);
131            driver.run(self).expect("Driver failed");
132            result.expect("Driver failed to poll")
133        })
134    }
135}
136
137impl Drop for Runtime {
138    fn drop(&mut self) {
139        CURRENT_RUNTIME.set(self, || {
140            self.queue.clear();
141        });
142    }
143}
144
145#[derive(Debug)]
146/// A thread-safe handle used to schedule work on a runtime.
147pub struct Handle {
148    queue: Arc<RunnableQueue>,
149}
150
151impl Handle {
152    /// Returns a handle to the runtime active on the current thread.
153    ///
154    /// # Panics
155    ///
156    /// Panics if no runtime is active on the current thread.
157    pub fn current() -> Handle {
158        Runtime::with_current(Runtime::handle)
159    }
160
161    /// Wakes the runtime's driver.
162    pub fn notify(&self) -> io::Result<()> {
163        self.queue.handle.notify()
164    }
165
166    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
167    ///
168    /// Spawning a task enables the task to execute concurrently to other tasks.
169    /// There is no guarantee that a spawned task will execute to completion.
170    pub fn spawn<F: Future + Send + 'static>(&self, future: F) -> JoinHandle<F::Output> {
171        let queue = self.queue.clone();
172        let schedule = move |runnable| {
173            queue.schedule(runnable);
174        };
175        let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
176        runnable.schedule();
177        JoinHandle::new(task)
178    }
179}
180
181impl Clone for Handle {
182    fn clone(&self) -> Self {
183        Self {
184            queue: self.queue.clone(),
185        }
186    }
187}
188
189#[derive(Debug)]
190struct RunnableQueue {
191    id: thread::ThreadId,
192    idle: Cell<bool>,
193    handle: Box<dyn Notify>,
194    event_interval: usize,
195    local_queue: UnsafeCell<VecDeque<Runnable>>,
196    sync_fixed_queue: Queue<ArrayBuffer<Runnable, 128>>,
197    sync_queue: SegQueue<Runnable>,
198}
199
200unsafe impl Send for RunnableQueue {}
201unsafe impl Sync for RunnableQueue {}
202
203impl RunnableQueue {
204    fn new(event_interval: usize, handle: Box<dyn Notify>) -> Self {
205        Self {
206            handle,
207            event_interval,
208            id: current_thread_id(),
209            idle: Cell::new(true),
210            local_queue: UnsafeCell::new(VecDeque::new()),
211            sync_fixed_queue: Queue::default(),
212            sync_queue: SegQueue::new(),
213        }
214    }
215
216    fn schedule(&self, runnable: Runnable) {
217        if self.id == current_thread_id() {
218            unsafe { (*self.local_queue.get()).push_back(runnable) };
219            if self.idle.get() {
220                self.idle.set(false);
221                self.handle.notify().ok();
222            }
223        } else {
224            let result = self.sync_fixed_queue.try_enqueue([runnable]);
225            if let Err(TryEnqueueError::InsufficientCapacity([runnable])) = result {
226                self.sync_queue.push(runnable);
227            }
228            self.handle.notify().ok();
229        }
230    }
231
232    fn run(&self) -> bool {
233        // a running task may schedule into `local_queue`, so it must not be
234        // borrowed across `task.run()`
235        let local_queue = {
236            for _ in 0..self.event_interval {
237                if let Some(task) = self.pop_local() {
238                    task.run();
239                } else {
240                    break;
241                }
242            }
243            unsafe { !(*self.local_queue.get()).is_empty() }
244        };
245
246        let sync_queue_fixed = match self.sync_fixed_queue.try_dequeue() {
247            Ok(buf) => {
248                for task in buf {
249                    task.run();
250                }
251                false
252            }
253            Err(TryDequeueError::Empty | TryDequeueError::Closed) => false,
254            Err(_) => true,
255        };
256
257        let sync_queue = {
258            for _ in 0..self.event_interval {
259                if let Some(task) = self.sync_queue.pop() {
260                    task.run();
261                } else {
262                    break;
263                }
264            }
265            !self.sync_queue.is_empty()
266        };
267
268        let more_tasks = local_queue || sync_queue_fixed || sync_queue;
269        if !more_tasks {
270            self.idle.set(true);
271        }
272        more_tasks
273    }
274
275    fn clear(&self) {
276        while self.sync_queue.pop().is_some() {}
277        while self.sync_fixed_queue.try_dequeue().is_ok() {}
278        // dropped tasks may schedule other tasks, drop each outside of the borrow
279        while let Some(task) = self.pop_local() {
280            drop(task);
281        }
282    }
283
284    fn pop_local(&self) -> Option<Runnable> {
285        unsafe { (*self.local_queue.get()).pop_front() }
286    }
287}
288
289/// Builder for [`Runtime`].
290#[derive(Debug, Clone)]
291pub struct RuntimeBuilder {
292    event_interval: usize,
293}
294
295impl Default for RuntimeBuilder {
296    fn default() -> Self {
297        Self::new()
298    }
299}
300
301impl RuntimeBuilder {
302    /// Create the builder with default config.
303    pub fn new() -> Self {
304        Self { event_interval: 61 }
305    }
306
307    /// Sets the number of scheduler ticks after which the scheduler will poll
308    /// for external events (timers, I/O, and so on).
309    ///
310    /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
311    /// Values below 1 are treated as 1.
312    pub fn event_interval(&mut self, val: usize) -> &mut Self {
313        self.event_interval = val.max(1);
314        self
315    }
316
317    /// Build [`Runtime`].
318    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
319        Runtime::with_builder(self, handle)
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use std::task::{Poll, Waker};
326    use std::{cell::RefCell, future::poll_fn, rc::Rc};
327
328    use super::*;
329
330    #[derive(Debug)]
331    struct NoopNotify;
332
333    impl Notify for NoopNotify {
334        fn notify(&self) -> io::Result<()> {
335            Ok(())
336        }
337    }
338
339    struct WakeOnDrop(Rc<RefCell<Option<Waker>>>);
340
341    impl Drop for WakeOnDrop {
342        fn drop(&mut self) {
343            if let Some(w) = self.0.borrow_mut().take() {
344                w.wake();
345            }
346        }
347    }
348
349    #[test]
350    fn schedule_while_running() {
351        let rt = Runtime::new(Box::new(NoopNotify));
352        let done = Rc::new(RefCell::new(0));
353        let done2 = done.clone();
354        rt.spawn(async move {
355            // schedules into the local queue from a running task
356            let h = Runtime::with_current(|rt| rt.spawn(async { 1 }));
357            *done2.borrow_mut() = h.await.unwrap();
358        })
359        .detach();
360        CURRENT_RUNTIME.set(&rt, || while rt.poll() == PollResult::PollAgain {});
361        assert_eq!(*done.borrow(), 1);
362    }
363
364    #[test]
365    fn event_interval() {
366        use std::sync::atomic::{AtomicUsize, Ordering};
367
368        for val in [0, 1] {
369            let rt = Runtime::builder()
370                .event_interval(val)
371                .build(Box::new(NoopNotify));
372            assert_eq!(rt.poll(), PollResult::Pending);
373
374            rt.spawn(async {}).detach();
375            rt.spawn(async {}).detach();
376            assert_eq!(rt.poll(), PollResult::PollAgain);
377            assert_eq!(rt.poll(), PollResult::Pending);
378        }
379
380        // tasks scheduled from other threads overflow the fixed queue
381        let rt = Runtime::builder()
382            .event_interval(1)
383            .build(Box::new(NoopNotify));
384        let cnt = Arc::new(AtomicUsize::new(0));
385        let hnd = rt.handle();
386        let cnt2 = cnt.clone();
387        std::thread::spawn(move || {
388            for _ in 0..130 {
389                let cnt = cnt2.clone();
390                hnd.spawn(async move {
391                    cnt.fetch_add(1, Ordering::Relaxed);
392                })
393                .detach();
394            }
395        })
396        .join()
397        .unwrap();
398        assert_eq!(rt.poll(), PollResult::PollAgain);
399        assert_eq!(cnt.load(Ordering::Relaxed), 129);
400        assert_eq!(rt.poll(), PollResult::Pending);
401        assert_eq!(cnt.load(Ordering::Relaxed), 130);
402    }
403
404    #[test]
405    fn schedule_while_clearing() {
406        let rt = Runtime::new(Box::new(NoopNotify));
407        let waker = Rc::new(RefCell::new(None));
408        let waker2 = waker.clone();
409        rt.spawn(poll_fn(move |cx| {
410            *waker2.borrow_mut() = Some(cx.waker().clone());
411            Poll::<()>::Pending
412        }))
413        .detach();
414        assert_eq!(rt.poll(), PollResult::Pending);
415        assert!(waker.borrow().is_some());
416
417        // queued task wakes the waiting task while the queue is cleared
418        let guard = WakeOnDrop(waker.clone());
419        rt.spawn(async move {
420            let _g = guard;
421        })
422        .detach();
423        rt.spawn(async {}).detach();
424        drop(rt);
425        assert!(waker.borrow().is_none());
426        // the woken task is dropped too, releasing its clone of `waker`
427        assert_eq!(Rc::strong_count(&waker), 1);
428    }
429}