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