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
14#[derive(Debug)]
15/// The async runtime for ntex.
16///
17/// This is a thread-local runtime and cannot be sent to other threads.
18pub struct Runtime {
19    stop: Cell<bool>,
20    queue: Arc<RunnableQueue>,
21}
22
23impl Runtime {
24    /// Create [`Runtime`] with default config.
25    pub fn new(handle: Box<dyn Notify>) -> Self {
26        Self::builder().build(handle)
27    }
28
29    /// Create a builder for [`Runtime`].
30    pub fn builder() -> RuntimeBuilder {
31        RuntimeBuilder::new()
32    }
33
34    #[allow(clippy::arc_with_non_send_sync)]
35    fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
36        Self {
37            stop: Cell::new(false),
38            queue: Arc::new(RunnableQueue::new(builder.event_interval, handle)),
39        }
40    }
41
42    /// Perform a function on the current runtime.
43    ///
44    /// ## Panics
45    ///
46    /// This method will panic if there are no running [`Runtime`].
47    pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
48        #[cold]
49        fn not_in_neon_runtime() -> ! {
50            panic!("not in a neon runtime")
51        }
52
53        if CURRENT_RUNTIME.is_set() {
54            CURRENT_RUNTIME.with(f)
55        } else {
56            not_in_neon_runtime()
57        }
58    }
59
60    #[inline]
61    /// Get handle for current runtime
62    pub fn handle(&self) -> Handle {
63        Handle {
64            queue: self.queue.clone(),
65        }
66    }
67
68    /// Spawns a new asynchronous task, returning a [`Task`] for it.
69    ///
70    /// Spawning a task enables the task to execute concurrently to other tasks.
71    /// There is no guarantee that a spawned task will execute to completion.
72    pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
73        unsafe { self.spawn_unchecked(future) }
74    }
75
76    /// Spawns a new asynchronous task, returning a [`Task`] for it.
77    ///
78    /// # Safety
79    ///
80    /// The caller should ensure the captured lifetime is long enough.
81    pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
82        let queue = self.queue.clone();
83        let (runnable, task) = unsafe {
84            async_task::spawn_unchecked(future, move |runnable| {
85                queue.schedule(runnable);
86            })
87        };
88        runnable.schedule();
89        JoinHandle::new(task)
90    }
91
92    /// Poll runtime and run active tasks.
93    pub fn poll(&self) -> PollResult {
94        if self.stop.get() {
95            PollResult::Ready
96        } else if self.queue.run() {
97            PollResult::PollAgain
98        } else {
99            PollResult::Pending
100        }
101    }
102
103    /// Runs the provided future.
104    ///
105    /// Blocks the current thread until the future completes.
106    ///
107    /// # Panics
108    ///
109    /// Panics if the driver fails to run the provided future.
110    pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
111        self.stop.set(false);
112
113        CURRENT_RUNTIME.set(self, || {
114            let mut result = None;
115            unsafe {
116                self.spawn_unchecked(async {
117                    result = Some(future.await);
118                    self.stop.set(true);
119                    let _ = self.queue.handle.notify();
120                });
121            }
122
123            ntex_error::set_backtrace_start_alt("src/raw.rs", 0);
124            driver.run(self).expect("Driver failed");
125            result.expect("Driver failed to poll")
126        })
127    }
128}
129
130impl Drop for Runtime {
131    fn drop(&mut self) {
132        CURRENT_RUNTIME.set(self, || {
133            self.queue.clear();
134        });
135    }
136}
137
138#[derive(Debug)]
139/// Handle for current runtime
140pub struct Handle {
141    queue: Arc<RunnableQueue>,
142}
143
144impl Handle {
145    /// Get handle for current runtime
146    ///
147    /// Panics if runtime is not set
148    pub fn current() -> Handle {
149        Runtime::with_current(Runtime::handle)
150    }
151
152    /// Wake up runtime
153    pub fn notify(&self) -> io::Result<()> {
154        self.queue.handle.notify()
155    }
156
157    /// Spawns a new asynchronous task, returning a [`Task`] for it.
158    ///
159    /// Spawning a task enables the task to execute concurrently to other tasks.
160    /// There is no guarantee that a spawned task will execute to completion.
161    pub fn spawn<F: Future + Send + 'static>(&self, future: F) -> JoinHandle<F::Output> {
162        let queue = self.queue.clone();
163        let schedule = move |runnable| {
164            queue.schedule(runnable);
165        };
166        let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
167        runnable.schedule();
168        JoinHandle::new(task)
169    }
170}
171
172impl Clone for Handle {
173    fn clone(&self) -> Self {
174        Self {
175            queue: self.queue.clone(),
176        }
177    }
178}
179
180#[derive(Debug)]
181struct RunnableQueue {
182    id: thread::ThreadId,
183    idle: Cell<bool>,
184    handle: Box<dyn Notify>,
185    event_interval: usize,
186    local_queue: UnsafeCell<VecDeque<Runnable>>,
187    sync_fixed_queue: Queue<ArrayBuffer<Runnable, 128>>,
188    sync_queue: SegQueue<Runnable>,
189}
190
191unsafe impl Send for RunnableQueue {}
192unsafe impl Sync for RunnableQueue {}
193
194impl RunnableQueue {
195    fn new(event_interval: usize, handle: Box<dyn Notify>) -> Self {
196        Self {
197            handle,
198            event_interval,
199            id: thread::current().id(),
200            idle: Cell::new(true),
201            local_queue: UnsafeCell::new(VecDeque::new()),
202            sync_fixed_queue: Queue::default(),
203            sync_queue: SegQueue::new(),
204        }
205    }
206
207    fn schedule(&self, runnable: Runnable) {
208        if self.id == thread::current().id() {
209            unsafe { (*self.local_queue.get()).push_back(runnable) };
210            if self.idle.get() {
211                self.idle.set(false);
212                self.handle.notify().ok();
213            }
214        } else {
215            let result = self.sync_fixed_queue.try_enqueue([runnable]);
216            if let Err(TryEnqueueError::InsufficientCapacity([runnable])) = result {
217                self.sync_queue.push(runnable);
218            }
219            self.handle.notify().ok();
220        }
221    }
222
223    fn run(&self) -> bool {
224        let local_queue = {
225            let q = unsafe { &mut *self.local_queue.get() };
226            for _ in 0..self.event_interval {
227                if let Some(task) = q.pop_front() {
228                    task.run();
229                } else {
230                    break;
231                }
232            }
233            !q.is_empty()
234        };
235
236        let sync_queue_fixed = match self.sync_fixed_queue.try_dequeue() {
237            Ok(buf) => {
238                for task in buf {
239                    task.run();
240                }
241                false
242            }
243            Err(TryDequeueError::Empty | TryDequeueError::Closed) => false,
244            Err(_) => true,
245        };
246
247        let mut idx = self.event_interval;
248        let sync_queue = loop {
249            idx -= 1;
250            if idx == 0 {
251                break true;
252            }
253            if !self.sync_queue.is_empty()
254                && let Some(task) = self.sync_queue.pop()
255            {
256                task.run();
257            } else {
258                break false;
259            }
260        };
261
262        let more_tasks = local_queue || sync_queue_fixed || sync_queue;
263        if !more_tasks {
264            self.idle.set(true);
265        }
266        more_tasks
267    }
268
269    fn clear(&self) {
270        while self.sync_queue.pop().is_some() {}
271        while self.sync_fixed_queue.try_dequeue().is_ok() {}
272        unsafe { (*self.local_queue.get()).clear() };
273    }
274}
275
276/// Builder for [`Runtime`].
277#[derive(Debug, Clone)]
278pub struct RuntimeBuilder {
279    event_interval: usize,
280}
281
282impl Default for RuntimeBuilder {
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288impl RuntimeBuilder {
289    /// Create the builder with default config.
290    pub fn new() -> Self {
291        Self { event_interval: 61 }
292    }
293
294    /// Sets the number of scheduler ticks after which the scheduler will poll
295    /// for external events (timers, I/O, and so on).
296    ///
297    /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
298    pub fn event_interval(&mut self, val: usize) -> &mut Self {
299        self.event_interval = val;
300        self
301    }
302
303    /// Build [`Runtime`].
304    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
305        Runtime::with_builder(self, handle)
306    }
307}