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