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