ntex_rt/lib.rs
1//! Single-threaded asynchronous runtime infrastructure for ntex.
2//!
3//! A [`System`] owns the runtime configuration and coordinates one or more
4//! [`Arbiter`] execution threads. Each arbiter runs local futures on its own
5//! thread, allowing tasks to use types that are not `Send`.
6//!
7//! Use [`System::build()`] to configure a system, [`spawn()`] to start a local
8//! task, and [`spawn_blocking()`] for blocking work.
9//!
10//! Local [`JoinHandle`]s are neither `Send` nor `Sync`, even for `Send` outputs.
11//! They detach on drop by default. Use [`JoinHandle::cancel_on_drop()`] to
12//! request cancellation on drop instead.
13//!
14//! Use [`spawn_sendable()`] to spawn a local future whose `Send` output can be
15//! awaited on another thread through a [`SendableJoinHandle`]. Dropping that
16//! handle detaches the task by default; use [`SendableJoinHandle::cancel()`] or
17//! [`SendableJoinHandle::cancel_on_drop()`] to request cancellation.
18//!
19//! [`Handle::spawn()`] submits a `Send` future to the handle's runtime and
20//! returns a [`RemoteJoinHandle`] for the calling thread. On the runtime's own thread
21//! the task is spawned directly. From other threads the future and later its
22//! `Send` output share one allocation, which is passed to the runtime thread:
23//!
24//! ```no_run
25//! # async fn example(handle: ntex_rt::Handle) {
26//! let task = handle.spawn(async { 42 });
27//! assert_eq!(task.await.unwrap(), 42);
28//! # }
29//! ```
30//!
31//! Dropping the handle detaches the task, [`RemoteJoinHandle::cancel()`] cancels it.
32//! A submitted future is polled and dropped on the runtime thread; shutdown or
33//! cancellation before producing a result yields a join error. No task is
34//! guaranteed to finish. Rejected submissions are never polled and their
35//! futures may be dropped on the submitting thread.
36//!
37//! Use [`Handle::spawn_detached()`] for fire-and-forget work. Its output need
38//! not be `Send` and is discarded on the owner.
39//!
40//! Remote join handles also remain on their calling thread, even when the
41//! submission uses the runtime's local fast path. Both the submitted future
42//! and its output must be `Send`:
43//!
44//! ```compile_fail
45//! # fn example(handle: ntex_rt::Handle) {
46//! handle.spawn(async { std::rc::Rc::new(42) });
47//! # }
48//! ```
49//!
50//! [`LocalWaker`] owns a thread-confined wake handle. It specializes native
51//! task wakes on their owner thread and preserves standard waker behavior
52//! for other runtimes and wrapped wakers.
53//!
54//! Runtime backends are selected with Cargo features:
55//!
56//! - no runtime feature uses the native ntex runtime
57//! - `tokio` uses the Tokio-based runtime adapter
58//! - `compio` uses the Compio-based runtime adapter
59//!
60//! The `tokio` and `compio` features are mutually exclusive. The [`rt`] module
61//! always exposes the native scheduler, including [`Runtime`] and
62//! [`RuntimeBuilder`], independently of which adapter supplies the
63//! crate-level spawning functions and [`Handle`].
64//!
65//! A [`Runner`] supplies the event loop used by [`SystemRunner`] and arbiter
66//! threads. Building a system only stores its configuration; execution begins
67//! when the returned system runner is driven.
68//!
69//! Native event-loop integrations implement [`Driver`] and use [`PollResult`]
70//! from [`Runtime::poll`] to decide whether to wait, poll again, or stop.
71//! Runtime, arbiter, and blocking pool metric types are grouped in [`metrics`].
72#![deny(clippy::pedantic)]
73#![allow(
74 clippy::missing_errors_doc,
75 clippy::missing_fields_in_debug,
76 clippy::must_use_candidate
77)]
78
79mod abort;
80mod arbiter;
81mod builder;
82mod busy;
83#[cfg(target_os = "linux")]
84mod capture;
85mod driver;
86mod handle;
87mod local_waker;
88pub mod metrics;
89mod pool;
90mod remote;
91pub mod rt;
92pub mod signals;
93mod system;
94mod task;
95#[cfg(test)]
96mod testing;
97mod watchdog;
98
99#[doc(hidden)]
100#[cfg(feature = "compio")]
101pub mod rt_compio;
102#[doc(hidden)]
103#[cfg(feature = "tokio")]
104pub mod rt_tokio;
105
106pub use self::abort::AbortOnUnwind;
107pub use self::arbiter::{Arbiter, get_item, remove_all_items, set_item, with_item};
108pub use self::builder::{Builder, SystemRunner};
109pub use self::driver::{BlockFuture, Driver, DriverType, Notify, PollResult, Runner};
110pub use self::local_waker::LocalWaker;
111pub use self::pool::{BlockingError, BlockingResult, ThreadPool, spawn_blocking};
112pub use self::rt::{Runtime, RuntimeBuilder};
113pub use self::system::{Id, System};
114pub use self::task::{SendableJoinHandle, spawn_sendable, task_callbacks, task_opt_callbacks};
115
116#[cfg(feature = "tokio")]
117pub use self::rt_tokio::*;
118
119#[cfg(all(feature = "compio", not(feature = "tokio")))]
120pub use self::rt_compio::*;
121
122#[cfg(all(not(feature = "tokio"), not(feature = "compio")))]
123pub use self::rt::{Handle, JoinError, JoinHandle, RemoteJoinHandle, now, spawn, spawn_detached};
124
125pub(crate) type HashMap<K, V> = std::collections::HashMap<K, V, foldhash::fast::RandomState>;
126
127/// Runs `f` while an earlier panic is pending, the process aborts if `f`
128/// panics.
129pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
130 struct Abort;
131
132 impl Drop for Abort {
133 fn drop(&mut self) {
134 abort_on_second_panic();
135 }
136 }
137
138 let guard = Abort;
139 let result = f();
140 std::mem::forget(guard);
141 result
142}
143
144/// Aborts the process on a panic raised while an earlier panic is unwinding
145/// or held to be resumed.
146#[cold]
147pub(crate) fn abort_on_second_panic() -> ! {
148 if let Err(payload) = std::panic::catch_unwind(|| {
149 log::error!("Panic during shutdown after an earlier panic; aborting");
150 }) {
151 std::mem::forget(payload);
152 }
153 std::process::abort();
154}