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    /// Creates a runtime with default configuration.
25    pub fn new(handle: Box<dyn Notify>) -> Self {
26        Self::builder().build(handle)
27    }
28
29    /// Creates a runtime builder.
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    /// Runs a closure with the runtime active on the current thread.
43    ///
44    /// ## Panics
45    ///
46    /// Panics if no runtime is active on the current thread.
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    /// Returns a handle to this 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 [`JoinHandle`] 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 [`JoinHandle`] 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    /// Polls the runtime and runs scheduled 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/// A thread-safe handle used to schedule work on a runtime.
140pub struct Handle {
141    queue: Arc<RunnableQueue>,
142}
143
144impl Handle {
145    /// Returns a handle to the runtime active on the current thread.
146    ///
147    /// # Panics
148    ///
149    /// Panics if no runtime is active on the current thread.
150    pub fn current() -> Handle {
151        Runtime::with_current(Runtime::handle)
152    }
153
154    /// Wakes the runtime's driver.
155    pub fn notify(&self) -> io::Result<()> {
156        self.queue.handle.notify()
157    }
158
159    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
160    ///
161    /// Spawning a task enables the task to execute concurrently to other tasks.
162    /// There is no guarantee that a spawned task will execute to completion.
163    pub fn spawn<F: Future + Send + 'static>(&self, future: F) -> JoinHandle<F::Output> {
164        let queue = self.queue.clone();
165        let schedule = move |runnable| {
166            queue.schedule(runnable);
167        };
168        let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
169        runnable.schedule();
170        JoinHandle::new(task)
171    }
172}
173
174impl Clone for Handle {
175    fn clone(&self) -> Self {
176        Self {
177            queue: self.queue.clone(),
178        }
179    }
180}
181
182#[derive(Debug)]
183struct RunnableQueue {
184    id: thread::ThreadId,
185    idle: Cell<bool>,
186    handle: Box<dyn Notify>,
187    event_interval: usize,
188    local_queue: UnsafeCell<VecDeque<Runnable>>,
189    sync_fixed_queue: Queue<ArrayBuffer<Runnable, 128>>,
190    sync_queue: SegQueue<Runnable>,
191}
192
193unsafe impl Send for RunnableQueue {}
194unsafe impl Sync for RunnableQueue {}
195
196impl RunnableQueue {
197    fn new(event_interval: usize, handle: Box<dyn Notify>) -> Self {
198        Self {
199            handle,
200            event_interval,
201            id: thread::current().id(),
202            idle: Cell::new(true),
203            local_queue: UnsafeCell::new(VecDeque::new()),
204            sync_fixed_queue: Queue::default(),
205            sync_queue: SegQueue::new(),
206        }
207    }
208
209    fn schedule(&self, runnable: Runnable) {
210        if self.id == thread::current().id() {
211            unsafe { (*self.local_queue.get()).push_back(runnable) };
212            if self.idle.get() {
213                self.idle.set(false);
214                self.handle.notify().ok();
215            }
216        } else {
217            let result = self.sync_fixed_queue.try_enqueue([runnable]);
218            if let Err(TryEnqueueError::InsufficientCapacity([runnable])) = result {
219                self.sync_queue.push(runnable);
220            }
221            self.handle.notify().ok();
222        }
223    }
224
225    fn run(&self) -> bool {
226        let local_queue = {
227            let q = unsafe { &mut *self.local_queue.get() };
228            for _ in 0..self.event_interval {
229                if let Some(task) = q.pop_front() {
230                    task.run();
231                } else {
232                    break;
233                }
234            }
235            !q.is_empty()
236        };
237
238        let sync_queue_fixed = match self.sync_fixed_queue.try_dequeue() {
239            Ok(buf) => {
240                for task in buf {
241                    task.run();
242                }
243                false
244            }
245            Err(TryDequeueError::Empty | TryDequeueError::Closed) => false,
246            Err(_) => true,
247        };
248
249        let mut idx = self.event_interval;
250        let sync_queue = loop {
251            idx -= 1;
252            if idx == 0 {
253                break true;
254            }
255            if !self.sync_queue.is_empty()
256                && let Some(task) = self.sync_queue.pop()
257            {
258                task.run();
259            } else {
260                break false;
261            }
262        };
263
264        let more_tasks = local_queue || sync_queue_fixed || sync_queue;
265        if !more_tasks {
266            self.idle.set(true);
267        }
268        more_tasks
269    }
270
271    fn clear(&self) {
272        while self.sync_queue.pop().is_some() {}
273        while self.sync_fixed_queue.try_dequeue().is_ok() {}
274        unsafe { (*self.local_queue.get()).clear() };
275    }
276}
277
278/// Builder for [`Runtime`].
279#[derive(Debug, Clone)]
280pub struct RuntimeBuilder {
281    event_interval: usize,
282}
283
284impl Default for RuntimeBuilder {
285    fn default() -> Self {
286        Self::new()
287    }
288}
289
290impl RuntimeBuilder {
291    /// Create the builder with default config.
292    pub fn new() -> Self {
293        Self { event_interval: 61 }
294    }
295
296    /// Sets the number of scheduler ticks after which the scheduler will poll
297    /// for external events (timers, I/O, and so on).
298    ///
299    /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
300    pub fn event_interval(&mut self, val: usize) -> &mut Self {
301        self.event_interval = val;
302        self
303    }
304
305    /// Build [`Runtime`].
306    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
307        Runtime::with_builder(self, handle)
308    }
309}