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