Skip to main content

ntex_rt/rt/
mod.rs

1//! Native single-threaded runtime, task spawning, handles, and clock access.
2//!
3//! This module always uses the native scheduler, regardless of which runtime
4//! backend is selected at the crate root. Its spawning functions and handles
5//! are re-exported at the crate root when neither `tokio` nor `compio` is enabled.
6//! The free spawning functions require an active [`Runtime`], not an active
7//! [`System`](crate::System).
8//!
9//! A [`Driver`] supplies the event loop and interprets the [`PollResult`]
10//! returned by [`Runtime::poll`]. [`Runtime::block_on`] installs the native
11//! runtime while that driver runs, even when Tokio or Compio is selected:
12//!
13//! ```no_run
14//! use ntex_rt::rt;
15//!
16//! # fn example(driver: impl ntex_rt::Driver) {
17//! let runtime = rt::Runtime::new(driver.handle());
18//! runtime.block_on(
19//!     async {
20//!         let task = rt::spawn(async { std::rc::Rc::new(42) });
21//!         assert_eq!(*task.await.unwrap(), 42);
22//!     },
23//!     &driver,
24//! );
25//! # }
26//! ```
27
28use std::cell::{Cell, RefCell};
29use std::future::poll_fn;
30use std::sync::atomic::Ordering;
31use std::task::Poll;
32use std::time::{Duration, Instant};
33use std::{fmt, future::Future, io, marker::PhantomData, panic::Location};
34use std::{pin::Pin, rc::Rc, sync::Arc};
35
36use crate::driver::{Driver, Notify, PollResult};
37use crate::handle::SpawnLocation;
38use crate::metrics::Histogram;
39use crate::remote::{Job, RemoteJoin, Runner};
40
41mod queue;
42pub(crate) mod task;
43
44use self::queue::RunnableQueue;
45
46pub use crate::handle::{JoinHandle, RemoteJoinHandle};
47
48/// Error returned by [`crate::spawn_sendable()`] when no task output is available.
49///
50/// Native [`JoinHandle`] and [`RemoteJoinHandle`] futures use a separate error
51/// type; this marker is not the error type of their results.
52#[derive(Debug, Copy, Clone)]
53pub struct JoinError;
54
55impl fmt::Display for JoinError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "JoinError")
58    }
59}
60
61impl std::error::Error for JoinError {}
62
63/// Returns the start of the current runtime turn.
64///
65/// Tasks polled in the same turn observe the same instant, see
66/// [`Runtime::now`]. Returns [`Instant::now()`] outside of a runtime.
67#[inline]
68pub fn now() -> Instant {
69    Runtime::current_now()
70}
71
72/// Spawns a future on the current native runtime and returns its join handle.
73///
74/// The future and its output need not be `Send`. Registered
75/// [`task_callbacks`](crate::task_callbacks) wrap the task. Dropping the handle
76/// detaches it unless [`cancel_on_drop`](JoinHandle::cancel_on_drop) is enabled.
77/// Completion is not guaranteed if the runtime shuts down.
78///
79/// # Panics
80///
81/// Panics if no native runtime is active on the current thread.
82#[track_caller]
83pub fn spawn<F>(fut: F) -> JoinHandle<F::Output>
84where
85    F: Future + 'static,
86{
87    let location = Location::caller();
88    let fut = crate::task::wrap(fut);
89    Runtime::with_current(|rt| unsafe { rt.spawn_at(fut, location) })
90}
91
92/// Spawns a future on the current thread without a join handle.
93///
94/// The future and its output need not be `Send`. The output is dropped on
95/// completion. Like [`spawn()`], this applies registered task callbacks, but
96/// does not create a join handle.
97///
98/// # Panics
99///
100/// Panics if no native runtime is active on the current thread.
101#[track_caller]
102pub fn spawn_detached<F>(fut: F)
103where
104    F: Future + 'static,
105{
106    let location = Location::caller();
107    let fut = crate::task::wrap(fut);
108    Runtime::with_current(|rt| unsafe { rt.spawn_detached_at(fut, location) });
109}
110
111/// Log target of the scheduler, also used by its submodules.
112const LOG_TARGET: &str = module_path!();
113
114// marks a reported self-wake streak
115pub(crate) const REPORTED: u32 = 1 << 31;
116
117/// Self-wake bookkeeping of a task, used on the runtime thread only.
118pub(crate) trait TaskStats: Copy {
119    /// Returns the task's spawn location for diagnostics.
120    fn location(self) -> SpawnLocation;
121    /// Consecutive self-wakes, `REPORTED` marks a reported streak.
122    fn self_wakes(self) -> u32;
123    /// Stores the self-wake count and its `REPORTED` marker.
124    fn set_self_wakes(self, wakes: u32);
125    /// Start of the self-wake streak, milliseconds since the runtime epoch.
126    fn streak_start(self) -> u32;
127    /// Records the streak's start in milliseconds since the runtime epoch.
128    fn set_streak_start(self, start: u32);
129}
130
131scoped_tls::scoped_thread_local!(static CURRENT_RUNTIME: Runtime);
132
133/// Index of a thread that owns a runtime, never reused within the process.
134#[cfg(not(ntex_loom))]
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub(crate) struct ThreadId(std::num::NonZeroU16);
137
138// loom threads share the OS thread, and its thread locals
139#[cfg(ntex_loom)]
140pub(crate) type ThreadId = loom::thread::ThreadId;
141
142/// The last issued thread index.
143#[cfg(not(ntex_loom))]
144static THREAD_INDEX: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(0);
145
146#[cfg(not(ntex_loom))]
147thread_local!(static THREAD_ID: Cell<Option<ThreadId>> = const { Cell::new(None) });
148
149/// Returns the index of the current thread, assigning one on first use.
150///
151/// Only threads that own a runtime call this, indexes are limited to
152/// `u16::MAX` per process and the process aborts when they run out.
153#[cfg(not(ntex_loom))]
154#[inline]
155pub(crate) fn current_thread_id() -> ThreadId {
156    /// Assigns a never-reused index on the thread's first use.
157    #[cold]
158    fn initialize(slot: &Cell<Option<ThreadId>>) -> ThreadId {
159        let id = THREAD_INDEX
160            .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
161            .ok()
162            .and_then(|n| std::num::NonZeroU16::new(n + 1))
163            .unwrap_or_else(|| {
164                log::error!(target: LOG_TARGET, "Thread indexes are exhausted, {} runtime-owning threads per process", u16::MAX);
165                std::process::abort()
166            });
167        let id = ThreadId(id);
168        slot.set(Some(id));
169        id
170    }
171
172    THREAD_ID.with(|slot| slot.get().unwrap_or_else(|| initialize(slot)))
173}
174
175/// Returns the index of the current thread without assigning one.
176#[cfg(not(ntex_loom))]
177#[inline]
178pub(crate) fn try_current_thread_id() -> Option<ThreadId> {
179    THREAD_ID.with(Cell::get)
180}
181
182/// Sets the last issued thread index, to test exhaustion.
183#[cfg(all(test, not(ntex_loom)))]
184pub(crate) fn set_thread_index(last: u16) {
185    THREAD_INDEX.store(last, Ordering::Relaxed);
186}
187
188/// Returns the current logical Loom thread's identity.
189#[cfg(ntex_loom)]
190pub(crate) fn current_thread_id() -> ThreadId {
191    loom::thread::current().id()
192}
193
194/// Returns the current logical Loom thread's identity.
195#[cfg(ntex_loom)]
196pub(crate) fn try_current_thread_id() -> Option<ThreadId> {
197    Some(loom::thread::current().id())
198}
199
200#[derive(Debug)]
201/// The native single-threaded task scheduler.
202///
203/// This is a thread-local runtime and cannot be sent to other threads, use
204/// [`Handle`] to spawn tasks from other threads.
205/// It is available even when the crate-level APIs select Tokio or Compio.
206/// A [`Driver`] supplies I/O and drives scheduled tasks through
207/// [`block_on`](Self::block_on).
208///
209/// Dropping the runtime cancels its tasks. If cleanup panics, shutdown finishes
210/// before the panic is resumed. A second cleanup panic, or a cleanup panic
211/// while the runtime is dropped during an existing unwind, aborts the process.
212///
213/// A process can create at most 65,535 runtimes, and at most 65,535 threads can
214/// create one. Neither is reused, and the process aborts when either runs out.
215///
216/// ```compile_fail
217/// fn assert_send<T: Send>() {}
218/// assert_send::<ntex_rt::rt::Runtime>();
219/// ```
220pub struct Runtime {
221    stop: Cell<bool>,
222    queue: Arc<RunnableQueue>,
223    // tasks are polled and dropped on the thread that created the runtime
224    _not_send: PhantomData<*const ()>,
225}
226
227impl Runtime {
228    /// Creates a runtime with default configuration.
229    ///
230    /// `handle` must wake the driver that will drive this runtime. Creating the
231    /// scheduler does not start an event loop or install it as the current runtime.
232    pub fn new(handle: Box<dyn Notify>) -> Self {
233        Self::builder().build(handle)
234    }
235
236    /// Creates a runtime builder.
237    pub fn builder() -> RuntimeBuilder {
238        RuntimeBuilder::new()
239    }
240
241    /// Creates the owner-thread queue and registers its remote wake endpoint.
242    #[allow(clippy::arc_with_non_send_sync)]
243    fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
244        let queue = Arc::new(RunnableQueue::new(builder, handle));
245        task::wake::register(&queue);
246        Self {
247            stop: Cell::new(false),
248            queue,
249            _not_send: PhantomData,
250        }
251    }
252
253    /// Calls `f` with a reference to the native runtime already active on this thread.
254    ///
255    /// # Panics
256    ///
257    /// Panics if no runtime is active on the current thread.
258    pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
259        /// Reports an operation that requires an active runtime.
260        #[cold]
261        fn not_in_neon_runtime() -> ! {
262            panic!("not in a neon runtime")
263        }
264
265        if CURRENT_RUNTIME.is_set() {
266            CURRENT_RUNTIME.with(f)
267        } else {
268            not_in_neon_runtime()
269        }
270    }
271
272    /// Returns [`Runtime::now`] of the runtime active on the current thread,
273    /// or [`Instant::now()`] if no runtime is active.
274    pub(crate) fn current_now() -> Instant {
275        if CURRENT_RUNTIME.is_set() {
276            CURRENT_RUNTIME.with(Runtime::now)
277        } else {
278            Instant::now()
279        }
280    }
281
282    #[inline]
283    /// Returns a handle to this runtime.
284    pub fn handle(&self) -> Handle {
285        Handle {
286            queue: self.queue.clone(),
287        }
288    }
289
290    /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
291    ///
292    /// Spawning a task enables the task to execute concurrently to other tasks.
293    /// There is no guarantee that a spawned task will execute to completion.
294    /// The future and output need not be `Send`. Unlike [`crate::rt::spawn()`],
295    /// this low-level method does not apply registered task callbacks.
296    #[track_caller]
297    pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
298        unsafe { self.spawn_at(future, Location::caller()) }
299    }
300
301    /// Spawns a task whose future may borrow non-`'static` data.
302    ///
303    /// Like [`spawn`](Self::spawn), this returns a local [`JoinHandle`] and
304    /// does not apply registered task callbacks.
305    ///
306    /// # Safety
307    ///
308    /// All data borrowed by the future must remain valid until the future is
309    /// completed or destroyed, including on unwind. Any borrowed data in the
310    /// output must remain valid until that output is destroyed, even if its join
311    /// handle outlives this runtime. Requesting cancellation, including through
312    /// [`JoinHandle::cancel_on_drop`], does not wait for destruction and is not
313    /// sufficient to end these obligations. Forgetting a join handle does not
314    /// request cancellation.
315    #[track_caller]
316    pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
317        unsafe { self.spawn_at(future, Location::caller()) }
318    }
319
320    /// Spawns a task, `location` is reported by [`Handle::current_task`].
321    ///
322    /// # Safety
323    ///
324    /// The caller should ensure the captured lifetime is long enough.
325    pub(crate) unsafe fn spawn_at<F: Future>(
326        &self,
327        future: F,
328        location: SpawnLocation,
329    ) -> JoinHandle<F::Output> {
330        unsafe { self.queue.spawn(future, location) }
331    }
332
333    /// Spawns a new asynchronous task without a [`JoinHandle`].
334    ///
335    /// The future and output need not be `Send`. The output is dropped on the
336    /// runtime thread when the task completes. This low-level method does not
337    /// apply registered task callbacks.
338    #[track_caller]
339    pub fn spawn_detached<F: Future + 'static>(&self, future: F) {
340        unsafe { self.spawn_detached_at(future, Location::caller()) }
341    }
342
343    /// Spawns a detached task, `location` is reported by
344    /// [`Handle::current_task`].
345    ///
346    /// # Safety
347    ///
348    /// The caller should ensure the captured lifetime is long enough.
349    pub(crate) unsafe fn spawn_detached_at<F: Future>(&self, future: F, location: SpawnLocation) {
350        unsafe { self.queue.spawn_detached::<_, true>(future, location) }
351    }
352
353    /// Marks the runtime as awake.
354    ///
355    /// Drivers call this after returning from a blocking wait, before handling
356    /// events that wake tasks. Tasks scheduled from the runtime thread do not
357    /// notify the driver until [`poll`](Self::poll) finds no more tasks to run,
358    /// the driver must call `poll` before blocking again.
359    ///
360    /// Starts a busy period, see [`busy_duration`](Self::busy_duration).
361    #[inline]
362    pub fn awake(&self) {
363        if self.queue.idle.get() {
364            self.queue.start_busy();
365        }
366    }
367
368    /// Polls the runtime and runs scheduled tasks.
369    ///
370    /// Runs bounded batches of ready tasks and remote submissions. Returns
371    /// [`PollResult::PollAgain`] if work remains, [`PollResult::Pending`] if the
372    /// driver may wait for events, or [`PollResult::Ready`] after the future
373    /// supplied to [`block_on`](Self::block_on) completes.
374    ///
375    /// This does not install the runtime as current; [`block_on`](Self::block_on)
376    /// establishes that context before invoking the driver.
377    pub fn poll(&self) -> PollResult {
378        let start = Instant::now();
379        // tasks scheduled from other threads do not end the idle state
380        if self.queue.idle.get() {
381            self.queue.busy.start(start);
382        }
383
384        let result = if self.stop.get() {
385            PollResult::Ready
386        } else if self.queue.run() {
387            self.queue.add_poll(true);
388            PollResult::PollAgain
389        } else {
390            self.queue.add_poll(false);
391            PollResult::Pending
392        };
393
394        // a runtime that never idles stays visible, its busy period is read
395        // from its start
396        let now = Instant::now();
397        if result != PollResult::Ready {
398            self.queue
399                .poll_histogram
400                .record(now.saturating_duration_since(start));
401        }
402        if result == PollResult::PollAgain {
403            self.queue.set_poll_start(now);
404            self.queue.busy.next_turn(now);
405        } else {
406            self.queue.idle.set(true);
407            self.queue.busy.stop(now);
408        }
409        result
410    }
411
412    /// Returns the start of the current runtime turn.
413    ///
414    /// While the runtime is busy, returns the time the driver woke up or the
415    /// previous [`poll`](Self::poll) finished, tasks polled in the same turn
416    /// observe the same instant. Returns [`Instant::now()`] while the runtime
417    /// is idle.
418    #[inline]
419    pub fn now(&self) -> Instant {
420        if self.queue.idle.get() {
421            Instant::now()
422        } else {
423            self.queue.poll_start.get()
424        }
425    }
426
427    /// Returns the total time the runtime has been busy.
428    ///
429    /// The runtime is busy from [`awake`](Self::awake) until [`poll`](Self::poll)
430    /// finds no more tasks to run. This includes running tasks and handling
431    /// driver events, and excludes time the driver is blocked waiting for events.
432    /// The value is wall-clock time, not CPU time, and includes the current
433    /// busy period, so it can be read from other threads while a task runs.
434    ///
435    /// Utilization over an interval is the busy duration delta divided by the
436    /// elapsed time of the interval.
437    pub fn busy_duration(&self) -> Duration {
438        self.queue.busy.busy_duration()
439    }
440
441    /// Returns how long the current runtime turn has been running, `None`
442    /// while the runtime is idle.
443    ///
444    /// A turn ends when [`poll`](Self::poll) returns, a long turn means a task
445    /// or a driver event handler keeps the runtime from making progress.
446    pub fn current_turn(&self) -> Option<Duration> {
447        self.queue.busy.current_turn()
448    }
449
450    /// Returns the number of scheduler polls, including polls with no ready tasks.
451    ///
452    /// Calls that immediately return [`PollResult::Ready`] are not counted.
453    pub fn poll_count(&self) -> u64 {
454        self.queue.polls.load(Ordering::Relaxed)
455    }
456
457    /// Returns the number of times [`poll`](Self::poll) returned
458    /// [`PollResult::PollAgain`].
459    ///
460    /// `poll` runs a limited number of tasks, and returns `PollAgain` if
461    /// scheduled tasks remain. The `poll_again_count` delta divided by the
462    /// [`poll_count`](Self::poll_count) delta over an interval is the share of
463    /// polls that left a backlog; a value close to 1 means tasks are scheduled
464    /// faster than the runtime runs them.
465    pub fn poll_again_count(&self) -> u64 {
466        self.queue.polls_again.load(Ordering::Relaxed)
467    }
468
469    /// Returns the number of times a task woke itself while it was polled.
470    ///
471    /// A task that wakes itself, for example with `wake_by_ref()` or a yield,
472    /// is scheduled again without waiting for an event. A steadily growing
473    /// count points to tasks that poll in a loop, see
474    /// [`RuntimeBuilder::self_wake_threshold`].
475    pub fn self_wake_count(&self) -> u64 {
476        self.queue.self_wakes.load(Ordering::Relaxed)
477    }
478
479    /// Returns the histogram of [`poll`](Self::poll) durations.
480    ///
481    /// Measures time spent inside `poll`, excluding driver-event processing
482    /// before the call. Calls returning [`PollResult::Ready`] are not recorded.
483    /// Long polls can delay driver events and increase scheduling latency.
484    pub fn poll_histogram(&self) -> Histogram {
485        self.queue.poll_histogram.snapshot()
486    }
487
488    /// Runs the provided future.
489    ///
490    /// Blocks the current thread until the future completes.
491    /// The future may borrow local data. Other tasks remain scheduled after
492    /// this call and may be driven by a later call; dropping the runtime cancels
493    /// its unfinished tasks. The supplied future is dropped before returning,
494    /// including if the driver fails or panics.
495    ///
496    /// # Panics
497    ///
498    /// Panics if the driver fails to complete the provided future, or if a runtime
499    /// is already active on the current thread. Nested calls would block the
500    /// running task and its runtime until the inner future completes.
501    #[track_caller]
502    pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
503        /// Rejects blocking from within an already active runtime.
504        #[cold]
505        #[track_caller]
506        fn nested_block_on() -> ! {
507            panic!("Cannot start a runtime from within a runtime")
508        }
509
510        if CURRENT_RUNTIME.is_set() {
511            nested_block_on()
512        }
513
514        let location = Location::caller();
515        self.stop.set(false);
516        self.queue.set_poll_start(Instant::now());
517
518        CURRENT_RUNTIME.set(self, || {
519            let mut result = None;
520
521            // the task outlives this call if the driver fails or panics, it
522            // reaches the borrowing future only through the slot, which the
523            // guard empties before the borrows end
524            let slot = BlockOnSlot(Rc::new(RefCell::new(Some(Box::pin(async {
525                result = Some(future.await);
526                self.stop.set(true);
527                self.queue.notify();
528            })))));
529            let task = slot.0.clone();
530            unsafe {
531                self.spawn_detached_at(
532                    poll_fn(move |cx| {
533                        let mut fut = task.borrow_mut();
534                        if let Some(f) = fut.as_mut()
535                            && f.as_mut().poll(cx).is_pending()
536                        {
537                            return Poll::Pending;
538                        }
539                        *fut = None;
540                        Poll::Ready(())
541                    }),
542                    location,
543                );
544            }
545
546            ntex_error::set_backtrace_start_alt("src/rt/task.rs", 0);
547            let res = driver.run(self);
548            drop(slot);
549            res.expect("Driver failed");
550            result.expect("Driver failed to poll")
551        })
552    }
553}
554
555/// Drops the [`Runtime::block_on`] future when the call returns or unwinds.
556struct BlockOnSlot<F>(Rc<RefCell<Option<Pin<Box<F>>>>>);
557
558impl<F> Drop for BlockOnSlot<F> {
559    /// Removes the borrowing future before the `block_on` scope ends.
560    fn drop(&mut self) {
561        let fut = self.0.borrow_mut().take();
562        drop(fut);
563    }
564}
565
566impl Drop for Runtime {
567    /// Cancels outstanding tasks with this runtime active during cleanup.
568    fn drop(&mut self) {
569        CURRENT_RUNTIME.set(self, || {
570            self.queue.shutdown();
571        });
572    }
573}
574
575#[derive(Debug)]
576/// A thread-safe handle used to schedule work on a runtime.
577pub struct Handle {
578    queue: Arc<RunnableQueue>,
579}
580
581impl Handle {
582    /// Returns a handle to the runtime active on the current thread.
583    ///
584    /// # Panics
585    ///
586    /// Panics if no runtime is active on the current thread.
587    pub fn current() -> Handle {
588        Runtime::with_current(Runtime::handle)
589    }
590
591    /// Wakes the runtime's driver.
592    ///
593    /// Returns notification errors to the caller without retrying, unlike
594    /// internal scheduler notifications.
595    pub fn notify(&self) -> io::Result<()> {
596        self.queue.handle.notify()
597    }
598
599    /// Returns the total time the runtime has been busy.
600    ///
601    /// See [`Runtime::busy_duration`]. Always returns `Some`, the return type
602    /// matches the other runtime backends.
603    pub fn busy_duration(&self) -> Option<Duration> {
604        Some(self.queue.busy.busy_duration())
605    }
606
607    /// Returns how long the current runtime turn has been running, `None`
608    /// while the runtime is idle.
609    ///
610    /// See [`Runtime::current_turn`].
611    pub fn current_turn(&self) -> Option<Duration> {
612        self.queue.busy.current_turn()
613    }
614
615    /// Returns the number of scheduler polls, including polls with no ready tasks.
616    ///
617    /// See [`Runtime::poll_count`]. Always returns `Some`.
618    pub fn poll_count(&self) -> Option<u64> {
619        Some(self.queue.polls.load(Ordering::Relaxed))
620    }
621
622    /// Returns the number of runtime polls that left scheduled tasks.
623    ///
624    /// See [`Runtime::poll_again_count`]. Always returns `Some`.
625    pub fn poll_again_count(&self) -> Option<u64> {
626        Some(self.queue.polls_again.load(Ordering::Relaxed))
627    }
628
629    /// Returns the number of times a task woke itself while it was polled.
630    ///
631    /// See [`Runtime::self_wake_count`]. Always returns `Some`.
632    pub fn self_wake_count(&self) -> Option<u64> {
633        Some(self.queue.self_wakes.load(Ordering::Relaxed))
634    }
635
636    /// Returns the spawn location of the task the runtime is polling.
637    ///
638    /// Returns `None` while the runtime is idle, handles driver events or
639    /// between tasks. Can be read from other threads, for example to name the
640    /// task that blocks a stalled runtime.
641    pub fn current_task(&self) -> Option<&'static Location<'static>> {
642        let task = self.queue.current.load(Ordering::Relaxed);
643        // only null or a `&'static Location` is stored
644        unsafe { task.as_ref() }
645    }
646
647    /// Returns the histogram of runtime poll durations.
648    ///
649    /// See [`Runtime::poll_histogram`]. Always returns `Some`.
650    pub fn poll_histogram(&self) -> Option<Histogram> {
651        Some(self.queue.poll_histogram.snapshot())
652    }
653
654    /// Spawns a task on the handle's runtime and returns a [`RemoteJoinHandle`].
655    ///
656    /// Spawning a task enables the task to execute concurrently to other tasks.
657    /// There is no guarantee that a spawned task will execute to completion.
658    ///
659    /// Both the future and output must be `Send`, including submissions from the
660    /// runtime's own thread, which use the local fast path. Accepted futures
661    /// are polled and dropped on the runtime thread; the output is transferred
662    /// to the caller through the join handle.
663    ///
664    /// The returned join handle is neither `Send` nor `Sync`; dropping it
665    /// detaches the task. Submissions rejected after shutdown are never polled
666    /// and their futures may be dropped on the submitting thread. This low-level
667    /// method does not apply registered task callbacks.
668    ///
669    /// ```no_run
670    /// # async fn check(hnd: ntex_rt::rt::Handle) {
671    /// let task = hnd.spawn(async { 42 });
672    /// assert_eq!(task.await.unwrap(), 42);
673    /// # }
674    /// ```
675    #[track_caller]
676    pub fn spawn<F>(&self, future: F) -> RemoteJoinHandle<F::Output>
677    where
678        F: Future + Send + 'static,
679        F::Output: Send + 'static,
680    {
681        let location = Location::caller();
682        if Some(self.queue.id) == try_current_thread_id() {
683            RemoteJoinHandle::local(unsafe { self.queue.spawn(future, location) })
684        } else {
685            let job = Job::new(future, true);
686            let join = RemoteJoin::new(job.clone());
687            // a rejected submission closes the job on this thread
688            unsafe {
689                self.queue.spawn_detached::<_, false>(Runner(job), location);
690            }
691            RemoteJoinHandle::remote(join)
692        }
693    }
694
695    /// Spawns a new asynchronous task without a join handle.
696    ///
697    /// The output is dropped on the runtime thread when the task completes,
698    /// so it does not have to be `Send`.
699    /// The future must be `Send`, even when submitted on the owner thread.
700    /// Rejected submissions are never polled and their futures may be dropped
701    /// on the submitting thread. Registered task callbacks are not applied.
702    #[track_caller]
703    pub fn spawn_detached<F>(&self, future: F)
704    where
705        F: Future + Send + 'static,
706    {
707        unsafe {
708            self.queue
709                .spawn_detached::<_, false>(future, Location::caller());
710        }
711    }
712}
713
714impl Clone for Handle {
715    /// Shares the runtime's queue without creating another runtime.
716    fn clone(&self) -> Self {
717        Self {
718            queue: self.queue.clone(),
719        }
720    }
721}
722
723/// Builder for [`Runtime`].
724#[derive(Debug, Clone)]
725pub struct RuntimeBuilder {
726    event_interval: usize,
727    self_wake_limit: u32,
728    self_wake_time: Duration,
729}
730
731impl Default for RuntimeBuilder {
732    /// Uses the default scheduler budgets and self-wake thresholds.
733    fn default() -> Self {
734        Self::new()
735    }
736}
737
738impl RuntimeBuilder {
739    /// Creates a builder with the default scheduler budgets and warning thresholds.
740    pub fn new() -> Self {
741        Self {
742            event_interval: 61,
743            self_wake_limit: 10_000,
744            self_wake_time: Duration::from_secs(1),
745        }
746    }
747
748    /// Sets the local task-poll budget for one [`Runtime::poll`] call.
749    ///
750    /// The driver can process external events between calls. Remote work is
751    /// serviced in separate batches, so this is not a strict limit on all work
752    /// done in one call. The default is 61; zero is treated as one.
753    pub fn event_interval(&mut self, val: usize) -> &mut Self {
754        self.event_interval = val.max(1);
755        self
756    }
757
758    /// Sets the self-wake streak that is reported as a possible busy loop.
759    ///
760    /// A task that wakes itself on every poll, for example a loop around
761    /// `wake_by_ref()` or a yield, never waits for an event. The runtime logs
762    /// a warning with the task spawn location once the task wakes itself
763    /// `count` times in a row and the streak lasts at least `duration`. Each
764    /// streak is reported once. A count of 0 disables the warning and per-task
765    /// streak tracking, but preserves [`Runtime::self_wake_count`].
766    ///
767    /// By default, 10 000 self-wakes over at least 1 second.
768    pub fn self_wake_threshold(&mut self, count: u32, duration: Duration) -> &mut Self {
769        self.self_wake_limit = count.min(!REPORTED);
770        self.self_wake_time = duration;
771        self
772    }
773
774    /// Creates a native runtime using this configuration and driver notifier.
775    ///
776    /// Does not start an event loop. The notifier must wake the driver used to
777    /// run this runtime.
778    pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
779        Runtime::with_builder(self, handle)
780    }
781}
782
783#[cfg(test)]
784#[path = "../../tests/rt/task_tests.rs"]
785mod task_tests;
786
787#[cfg(all(test, ntex_loom))]
788#[path = "../../tests/rt/task_loom.rs"]
789mod task_loom;
790
791#[cfg(test)]
792#[path = "../../tests/rt/tests.rs"]
793mod tests;