Skip to main content

ntex_rt/rt/
mod.rs

1use std::cell::{Cell, RefCell};
2use std::future::poll_fn;
3use std::sync::atomic::Ordering;
4use std::task::Poll;
5use std::time::{Duration, Instant};
6use std::{future::Future, io, marker::PhantomData, panic::Location};
7use std::{pin::Pin, rc::Rc, sync::Arc};
8
9use crate::driver::{Driver, Notify, PollResult};
10use crate::handle::{JoinHandle, SpawnLocation};
11use crate::metrics::Histogram;
12
13#[cfg(not(ntex_loom))]
14use std::thread;
15
16mod queue;
17pub(crate) mod task;
18
19use self::queue::{CacheLine, RunnableQueue, suppress_cleanup_panic};
20
21/// Log target of the scheduler, also used by its submodules.
22const LOG_TARGET: &str = module_path!();
23
24// marks a reported self-wake streak
25pub(crate) const REPORTED: u32 = 1 << 31;
26
27/// Self-wake bookkeeping of a task, used on the runtime thread only.
28pub(crate) trait TaskStats: Copy {
29    fn location(self) -> SpawnLocation;
30    /// Consecutive self-wakes, `REPORTED` marks a reported streak.
31    fn self_wakes(self) -> u32;
32    fn set_self_wakes(self, wakes: u32);
33    /// Start of the self-wake streak, milliseconds since the runtime epoch.
34    fn streak_start(self) -> u32;
35    fn set_streak_start(self, start: u32);
36}
37
38scoped_tls::scoped_thread_local!(static CURRENT_RUNTIME: Runtime);
39
40#[cfg(not(ntex_loom))]
41pub(crate) type ThreadId = thread::ThreadId;
42
43// loom threads share the OS thread, and its thread locals
44#[cfg(ntex_loom)]
45pub(crate) type ThreadId = loom::thread::ThreadId;
46
47#[cfg(not(ntex_loom))]
48thread_local!(static THREAD_ID: Cell<Option<ThreadId>> = const { Cell::new(None) });
49
50/// Returns the cached id of the current thread.
51#[cfg(not(ntex_loom))]
52#[inline]
53pub(crate) fn current_thread_id() -> ThreadId {
54    #[cold]
55    fn initialize(slot: &Cell<Option<ThreadId>>) -> ThreadId {
56        let id = thread::current().id();
57        slot.set(Some(id));
58        id
59    }
60
61    THREAD_ID.with(|slot| slot.get().unwrap_or_else(|| initialize(slot)))
62}
63
64#[cfg(ntex_loom)]
65pub(crate) fn current_thread_id() -> ThreadId {
66    loom::thread::current().id()
67}
68
69#[derive(Debug)]
70/// The async runtime for ntex.
71///
72/// This is a thread-local runtime and cannot be sent to other threads, use
73/// [`Handle`] to spawn tasks from other threads.
74///
75/// Dropping the runtime cancels its tasks. If cleanup panics, shutdown finishes
76/// before the first panic is resumed. Secondary cleanup panics are logged and
77/// suppressed, including when the runtime is dropped during an existing unwind.
78///
79/// ```compile_fail
80/// fn assert_send<T: Send>() {}
81/// assert_send::<ntex_rt::Runtime>();
82/// ```
83pub struct Runtime {
84    stop: Cell<bool>,
85    queue: Arc<RunnableQueue>,
86    // tasks are polled and dropped on the thread that created the runtime
87    _not_send: PhantomData<*const ()>,
88}
89
90impl Runtime {
91    /// Creates a runtime with default configuration.
92    pub fn new(handle: Box<dyn Notify>) -> Self {
93        Self::builder().build(handle)
94    }
95
96    /// Creates a runtime builder.
97    pub fn builder() -> RuntimeBuilder {
98        RuntimeBuilder::new()
99    }
100
101    #[allow(clippy::arc_with_non_send_sync)]
102    fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
103        Self {
104            stop: Cell::new(false),
105            queue: Arc::new(RunnableQueue::new(builder, handle)),
106            _not_send: PhantomData,
107        }
108    }
109
110    /// Runs a closure with the runtime active on the current thread.
111    ///
112    /// ## Panics
113    ///
114    /// Panics if no runtime is active on the current thread.
115    pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
116        #[cold]
117        fn not_in_neon_runtime() -> ! {
118            panic!("not in a neon runtime")
119        }
120
121        if CURRENT_RUNTIME.is_set() {
122            CURRENT_RUNTIME.with(f)
123        } else {
124            not_in_neon_runtime()
125        }
126    }
127
128    /// Returns [`Runtime::now`] of the runtime active on the current thread,
129    /// or [`Instant::now()`] if no runtime is active.
130    pub(crate) fn current_now() -> Instant {
131        if CURRENT_RUNTIME.is_set() {
132            CURRENT_RUNTIME.with(Runtime::now)
133        } else {
134            Instant::now()
135        }
136    }
137
138    #[inline]
139    /// Returns a handle to this runtime.
140    pub fn handle(&self) -> Handle {
141        Handle {
142            queue: self.queue.clone(),
143        }
144    }
145
146    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
147    ///
148    /// Spawning a task enables the task to execute concurrently to other tasks.
149    /// There is no guarantee that a spawned task will execute to completion.
150    #[track_caller]
151    pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
152        unsafe { self.spawn_at(future, Location::caller()) }
153    }
154
155    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
156    ///
157    /// # Safety
158    ///
159    /// The caller should ensure the captured lifetime is long enough.
160    #[track_caller]
161    pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
162        unsafe { self.spawn_at(future, Location::caller()) }
163    }
164
165    /// Spawns a task, `location` is reported by [`Handle::current_task`].
166    ///
167    /// # Safety
168    ///
169    /// The caller should ensure the captured lifetime is long enough.
170    pub(crate) unsafe fn spawn_at<F: Future>(
171        &self,
172        future: F,
173        location: SpawnLocation,
174    ) -> JoinHandle<F::Output> {
175        unsafe { self.queue.spawn::<_, true>(future, location) }
176    }
177
178    /// Spawns a new asynchronous task without a [`JoinHandle`].
179    ///
180    /// The output is dropped when the task completes.
181    #[track_caller]
182    pub fn spawn_detached<F: Future + 'static>(&self, future: F) {
183        unsafe { self.spawn_detached_at(future, Location::caller()) }
184    }
185
186    /// Spawns a detached task, `location` is reported by
187    /// [`Handle::current_task`].
188    ///
189    /// # Safety
190    ///
191    /// The caller should ensure the captured lifetime is long enough.
192    pub(crate) unsafe fn spawn_detached_at<F: Future>(&self, future: F, location: SpawnLocation) {
193        unsafe { self.queue.spawn_detached::<_, true>(future, location) }
194    }
195
196    /// Marks the runtime as awake.
197    ///
198    /// Drivers call this after returning from a blocking wait, before handling
199    /// events that wake tasks. Tasks scheduled from the runtime thread do not
200    /// notify the driver until [`poll`](Self::poll) finds no more tasks to run,
201    /// the driver must call `poll` before blocking again.
202    ///
203    /// Starts a busy period, see [`busy_duration`](Self::busy_duration).
204    #[inline]
205    pub fn awake(&self) {
206        if self.queue.idle.get() {
207            self.queue.start_busy();
208        }
209    }
210
211    /// Polls the runtime and runs scheduled tasks.
212    pub fn poll(&self) -> PollResult {
213        let start = Instant::now();
214        // tasks scheduled from other threads do not end the idle state
215        if self.queue.idle.get() {
216            self.queue.busy.start(start);
217        }
218
219        let result = if self.stop.get() {
220            PollResult::Ready
221        } else if self.queue.run() {
222            self.queue.add_poll(true);
223            PollResult::PollAgain
224        } else {
225            self.queue.add_poll(false);
226            PollResult::Pending
227        };
228
229        // a runtime that never idles stays visible, its busy period is read
230        // from its start
231        let now = Instant::now();
232        if result != PollResult::Ready {
233            self.queue
234                .poll_histogram
235                .record(now.saturating_duration_since(start));
236        }
237        if result == PollResult::PollAgain {
238            self.queue.set_poll_start(now);
239            self.queue.busy.next_turn(now);
240        } else {
241            self.queue.idle.set(true);
242            self.queue.busy.stop(now);
243        }
244        result
245    }
246
247    /// Returns the start of the current runtime turn.
248    ///
249    /// While the runtime is busy, returns the time the driver woke up or the
250    /// previous [`poll`](Self::poll) finished, tasks polled in the same turn
251    /// observe the same instant. Returns [`Instant::now()`] while the runtime
252    /// is idle.
253    #[inline]
254    pub fn now(&self) -> Instant {
255        if self.queue.idle.get() {
256            Instant::now()
257        } else {
258            self.queue.poll_start.get()
259        }
260    }
261
262    /// Returns the total time the runtime has been busy.
263    ///
264    /// The runtime is busy from [`awake`](Self::awake) until [`poll`](Self::poll)
265    /// finds no more tasks to run. This includes running tasks and handling
266    /// driver events, and excludes time the driver is blocked waiting for events.
267    /// The value is wall-clock time, not CPU time, and includes the current
268    /// busy period, so it can be read from other threads while a task runs.
269    ///
270    /// Utilization over an interval is the busy duration delta divided by the
271    /// elapsed time of the interval.
272    pub fn busy_duration(&self) -> Duration {
273        self.queue.busy.busy_duration()
274    }
275
276    /// Returns how long the current runtime turn has been running, `None`
277    /// while the runtime is idle.
278    ///
279    /// A turn ends when [`poll`](Self::poll) returns, a long turn means a task
280    /// or a driver event handler keeps the runtime from making progress.
281    pub fn current_turn(&self) -> Option<Duration> {
282        self.queue.busy.current_turn()
283    }
284
285    /// Returns the number of times [`poll`](Self::poll) ran scheduled tasks.
286    pub fn poll_count(&self) -> u64 {
287        self.queue.polls.load(Ordering::Relaxed)
288    }
289
290    /// Returns the number of times [`poll`](Self::poll) returned
291    /// [`PollResult::PollAgain`].
292    ///
293    /// `poll` runs a limited number of tasks, and returns `PollAgain` if
294    /// scheduled tasks remain. The `poll_again_count` delta divided by the
295    /// [`poll_count`](Self::poll_count) delta over an interval is the share of
296    /// polls that left a backlog; a value close to 1 means tasks are scheduled
297    /// faster than the runtime runs them.
298    pub fn poll_again_count(&self) -> u64 {
299        self.queue.polls_again.load(Ordering::Relaxed)
300    }
301
302    /// Returns the number of times a task woke itself while it was polled.
303    ///
304    /// A task that wakes itself, for example with `wake_by_ref()` or a yield,
305    /// is scheduled again without waiting for an event. A steadily growing
306    /// count points to tasks that poll in a loop, see
307    /// [`RuntimeBuilder::self_wake_threshold`].
308    pub fn self_wake_count(&self) -> u64 {
309        self.queue.self_wakes.load(Ordering::Relaxed)
310    }
311
312    /// Returns the histogram of [`poll`](Self::poll) durations.
313    ///
314    /// A poll lasts from the start of the runtime turn, see [`now`](Self::now),
315    /// until it finishes running tasks. Tasks woken during a poll wait at least
316    /// until it finishes, long polls mean high scheduling latency.
317    pub fn poll_histogram(&self) -> Histogram {
318        self.queue.poll_histogram.snapshot()
319    }
320
321    /// Runs the provided future.
322    ///
323    /// Blocks the current thread until the future completes.
324    ///
325    /// # Panics
326    ///
327    /// Panics if the driver fails to run the provided future, or if a runtime
328    /// is already active on the current thread. Nested calls would block the
329    /// running task and its runtime until the inner future completes.
330    #[track_caller]
331    pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
332        #[cold]
333        #[track_caller]
334        fn nested_block_on() -> ! {
335            panic!("Cannot start a runtime from within a runtime")
336        }
337
338        if CURRENT_RUNTIME.is_set() {
339            nested_block_on()
340        }
341
342        let location = Location::caller();
343        self.stop.set(false);
344        self.queue.set_poll_start(Instant::now());
345
346        CURRENT_RUNTIME.set(self, || {
347            let mut result = None;
348
349            // the task outlives this call if the driver fails or panics, it
350            // reaches the borrowing future only through the slot, which the
351            // guard empties before the borrows end
352            let slot = BlockOnSlot(Rc::new(RefCell::new(Some(Box::pin(async {
353                result = Some(future.await);
354                self.stop.set(true);
355                self.queue.notify();
356            })))));
357            let task = slot.0.clone();
358            unsafe {
359                self.spawn_detached_at(
360                    poll_fn(move |cx| {
361                        let mut fut = task.borrow_mut();
362                        if let Some(f) = fut.as_mut()
363                            && f.as_mut().poll(cx).is_pending()
364                        {
365                            return Poll::Pending;
366                        }
367                        *fut = None;
368                        Poll::Ready(())
369                    }),
370                    location,
371                );
372            }
373
374            ntex_error::set_backtrace_start_alt("src/rt/task.rs", 0);
375            let res = driver.run(self);
376            drop(slot);
377            res.expect("Driver failed");
378            result.expect("Driver failed to poll")
379        })
380    }
381}
382
383/// Drops the [`Runtime::block_on`] future when the call returns or unwinds.
384struct BlockOnSlot<F>(Rc<RefCell<Option<Pin<Box<F>>>>>);
385
386impl<F> Drop for BlockOnSlot<F> {
387    fn drop(&mut self) {
388        let fut = self.0.borrow_mut().take();
389        drop(fut);
390    }
391}
392
393impl Drop for Runtime {
394    fn drop(&mut self) {
395        CURRENT_RUNTIME.set(self, || {
396            self.queue.shutdown();
397        });
398    }
399}
400
401#[derive(Debug)]
402/// A thread-safe handle used to schedule work on a runtime.
403pub struct Handle {
404    queue: Arc<RunnableQueue>,
405}
406
407impl Handle {
408    /// Returns a handle to the runtime active on the current thread.
409    ///
410    /// # Panics
411    ///
412    /// Panics if no runtime is active on the current thread.
413    pub fn current() -> Handle {
414        Runtime::with_current(Runtime::handle)
415    }
416
417    /// Wakes the runtime's driver.
418    ///
419    /// Returns notification errors to the caller without retrying, unlike
420    /// internal scheduler notifications.
421    pub fn notify(&self) -> io::Result<()> {
422        self.queue.handle.notify()
423    }
424
425    /// Returns the total time the runtime has been busy.
426    ///
427    /// See [`Runtime::busy_duration`]. Always returns `Some`, the return type
428    /// matches the other runtime backends.
429    pub fn busy_duration(&self) -> Option<Duration> {
430        Some(self.queue.busy.busy_duration())
431    }
432
433    /// Returns how long the current runtime turn has been running, `None`
434    /// while the runtime is idle.
435    ///
436    /// See [`Runtime::current_turn`].
437    pub fn current_turn(&self) -> Option<Duration> {
438        self.queue.busy.current_turn()
439    }
440
441    /// Returns the number of runtime polls that ran scheduled tasks.
442    ///
443    /// See [`Runtime::poll_count`]. Always returns `Some`.
444    pub fn poll_count(&self) -> Option<u64> {
445        Some(self.queue.polls.load(Ordering::Relaxed))
446    }
447
448    /// Returns the number of runtime polls that left scheduled tasks.
449    ///
450    /// See [`Runtime::poll_again_count`]. Always returns `Some`.
451    pub fn poll_again_count(&self) -> Option<u64> {
452        Some(self.queue.polls_again.load(Ordering::Relaxed))
453    }
454
455    /// Returns the number of times a task woke itself while it was polled.
456    ///
457    /// See [`Runtime::self_wake_count`]. Always returns `Some`.
458    pub fn self_wake_count(&self) -> Option<u64> {
459        Some(self.queue.self_wakes.load(Ordering::Relaxed))
460    }
461
462    /// Returns the spawn location of the task the runtime is polling.
463    ///
464    /// Returns `None` while the runtime is idle, handles driver events or
465    /// between tasks. Can be read from other threads, for example to name the
466    /// task that blocks a stalled runtime.
467    pub fn current_task(&self) -> Option<&'static Location<'static>> {
468        let task = self.queue.current.load(Ordering::Relaxed);
469        // only null or a `&'static Location` is stored
470        unsafe { task.as_ref() }
471    }
472
473    /// Returns the histogram of runtime poll durations.
474    ///
475    /// See [`Runtime::poll_histogram`]. Always returns `Some`.
476    pub fn poll_histogram(&self) -> Option<Histogram> {
477        Some(self.queue.poll_histogram.snapshot())
478    }
479
480    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
481    ///
482    /// Spawning a task enables the task to execute concurrently to other tasks.
483    /// There is no guarantee that a spawned task will execute to completion.
484    ///
485    /// The output is moved to the thread that awaits the [`JoinHandle`], so
486    /// it must be `Send`. Use [`Runtime::spawn`] for non-`Send` outputs.
487    ///
488    /// ```compile_fail
489    /// # fn check(hnd: ntex_rt::Handle) {
490    /// let _ = hnd.spawn(async { std::rc::Rc::new(()) });
491    /// # }
492    /// ```
493    #[track_caller]
494    pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
495    where
496        F: Future + Send + 'static,
497        F::Output: Send,
498    {
499        unsafe { self.queue.spawn::<_, false>(future, Location::caller()) }
500    }
501
502    /// Spawns a new asynchronous task without a [`JoinHandle`].
503    ///
504    /// The output is dropped on the runtime thread when the task completes,
505    /// so it does not have to be `Send`.
506    #[track_caller]
507    pub fn spawn_detached<F>(&self, future: F)
508    where
509        F: Future + Send + 'static,
510    {
511        unsafe {
512            self.queue
513                .spawn_detached::<_, false>(future, Location::caller());
514        }
515    }
516}
517
518impl Clone for Handle {
519    fn clone(&self) -> Self {
520        Self {
521            queue: self.queue.clone(),
522        }
523    }
524}
525
526/// Builder for [`Runtime`].
527#[derive(Debug, Clone)]
528pub struct RuntimeBuilder {
529    event_interval: usize,
530    self_wake_limit: u32,
531    self_wake_time: Duration,
532}
533
534impl Default for RuntimeBuilder {
535    fn default() -> Self {
536        Self::new()
537    }
538}
539
540impl RuntimeBuilder {
541    /// Create the builder with default config.
542    pub fn new() -> Self {
543        Self {
544            event_interval: 61,
545            self_wake_limit: 10_000,
546            self_wake_time: Duration::from_secs(1),
547        }
548    }
549
550    /// Sets the number of scheduler ticks after which the scheduler will poll
551    /// for external events (timers, I/O, and so on).
552    ///
553    /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
554    /// Values below 1 are treated as 1.
555    pub fn event_interval(&mut self, val: usize) -> &mut Self {
556        self.event_interval = val.max(1);
557        self
558    }
559
560    /// Sets the self-wake streak that is reported as a possible busy loop.
561    ///
562    /// A task that wakes itself on every poll, for example a loop around
563    /// `wake_by_ref()` or a yield, never waits for an event. The runtime logs
564    /// a warning with the task spawn location once the task wakes itself
565    /// `count` times in a row and the streak lasts at least `duration`. Each
566    /// streak is reported once. A count of 0 disables the warning and per-task
567    /// streak tracking, but preserves [`Runtime::self_wake_count`].
568    ///
569    /// By default, 10 000 self-wakes over at least 1 second.
570    pub fn self_wake_threshold(&mut self, count: u32, duration: Duration) -> &mut Self {
571        self.self_wake_limit = count.min(!REPORTED);
572        self.self_wake_time = duration;
573        self
574    }
575
576    /// Build [`Runtime`].
577    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
578        Runtime::with_builder(self, handle)
579    }
580}
581
582#[cfg(test)]
583mod task_tests;
584
585#[cfg(all(test, ntex_loom))]
586mod task_loom;
587
588#[cfg(test)]
589mod tests;