ntex_rt/driver.rs
1//! Interfaces between the runtime and its event-loop driver.
2use std::{any::Any, cell::RefCell, fmt, io, panic, pin::Pin, rc::Rc};
3
4use crate::rt::Runtime;
5
6/// A boxed, pinned future passed to a [`Runner`].
7///
8/// The future may contain thread-local state and is not required to be `Send`.
9pub type BlockFuture = Pin<Box<dyn Future<Output = ()>>>;
10
11/// Operating-system I/O mechanism used by a driver.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum DriverType {
14 /// Readiness-based polling, such as epoll or kqueue.
15 Poll,
16 /// Linux io-uring.
17 IoUring,
18 /// Windows I/O completion ports.
19 Iocp,
20}
21
22impl DriverType {
23 /// Returns the driver name: `"polling"`, `"io-uring"`, or `"iocp"`.
24 pub const fn name(&self) -> &'static str {
25 match self {
26 DriverType::Poll => "polling",
27 DriverType::IoUring => "io-uring",
28 DriverType::Iocp => "iocp",
29 }
30 }
31
32 /// Returns whether this is a readiness-based polling driver.
33 pub const fn is_polling(&self) -> bool {
34 matches!(self, &DriverType::Poll)
35 }
36}
37
38/// Creates and drives an event loop for a system or arbiter thread.
39///
40/// A system shares its runner between arbiters. Implementations must support
41/// calls from multiple threads, while each supplied future stays on its calling
42/// thread.
43pub trait Runner: Send + Sync + 'static {
44 /// Runs the future to completion.
45 ///
46 /// Return `Ok(())` only after the future completes. A panic of the event loop
47 /// may be returned as an error containing its panic payload or may unwind;
48 /// the system performs cleanup and resumes the panic in either case.
49 fn block_on(&self, fut: BlockFuture) -> Result<(), Box<dyn Any + Send>>;
50}
51
52/// Drives I/O events and scheduled tasks for the native [`Runtime`].
53///
54/// Unlike [`Runner`], a driver belongs to a single runtime thread.
55pub trait Driver: 'static {
56 /// Returns a thread-safe notifier for this driver's event loop.
57 fn handle(&self) -> Box<dyn Notify>;
58
59 /// Drives the runtime until [`Runtime::poll`] returns [`PollResult::Ready`].
60 ///
61 /// Called by [`Runtime::block_on`] with `rt` active on the current thread.
62 /// Process external events between polls, without blocking when the result
63 /// is [`PollResult::PollAgain`]. Call [`Runtime::awake`] after waiting for
64 /// events, before dispatching events that may wake tasks.
65 ///
66 /// Returns an error if the event loop cannot continue.
67 fn run(&self, rt: &Runtime) -> io::Result<()>;
68
69 /// Releases driver-owned state when requested by its owner.
70 ///
71 /// The default implementation does nothing. [`Runtime::block_on`] does not
72 /// call this hook.
73 fn clear(&self) {}
74}
75
76/// Outcome of one call to [`Runtime::poll`].
77///
78/// Part of the native driver interface: [`Driver::run`] implementations inspect
79/// this result to decide whether to stop, wait for events, or run another
80/// nonblocking iteration.
81#[derive(Copy, Clone, Debug, PartialEq, Eq)]
82pub enum PollResult {
83 /// The future supplied to [`Runtime::block_on`] has completed; stop driving.
84 Ready,
85 /// No work is immediately runnable; the driver may wait for events.
86 Pending,
87 /// Work remains; process external events without blocking, then poll again.
88 PollAgain,
89}
90
91/// Wakes a runtime driver, retaining the wakeup if it has not blocked yet.
92///
93/// Internal scheduler notifications retry [`io::ErrorKind::Interrupted`] and
94/// log and abort the process on any other error or if `notify` panics, since
95/// queued work may otherwise remain stranded. Explicit notifications through
96/// [`Runtime::handle`] return errors and propagate panics to their caller.
97pub trait Notify: Send + Sync + fmt::Debug + 'static {
98 /// Wakes the driver or records a notification for its next wait.
99 ///
100 /// Notifications may be coalesced, but a notification racing with entry
101 /// into a blocking wait must not be lost. Returns an error if notification
102 /// fails.
103 fn notify(&self) -> io::Result<()>;
104}
105
106/// Executes a Windows system call and converts its failure sentinel to an I/O error.
107///
108/// The `BOOL` and `SOCKET` forms check for zero and nonzero respectively;
109/// `HANDLE` checks for `INVALID_HANDLE_VALUE`. The general form accepts an
110/// explicit comparison, as in `syscall!(expression, == 0)`.
111#[cfg(windows)]
112#[macro_export]
113macro_rules! syscall {
114 (BOOL, $e:expr) => {
115 $crate::syscall!($e, == 0)
116 };
117 (SOCKET, $e:expr) => {
118 $crate::syscall!($e, != 0)
119 };
120 (HANDLE, $e:expr) => {
121 $crate::syscall!($e, == ::windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE)
122 };
123 ($e:expr, $op: tt $rhs: expr) => {{
124 #[allow(unused_unsafe)]
125 let res = unsafe { $e };
126 if res $op $rhs {
127 Err(::std::io::Error::last_os_error())
128 } else {
129 Ok(res)
130 }
131 }};
132}
133
134/// Executes a Unix system call and converts a `-1` result to the last I/O error.
135///
136/// The `break` form retries interruptions and returns [`std::task::Poll::Pending`]
137/// for `WouldBlock` or `EINPROGRESS`, otherwise returning a ready result.
138#[cfg(unix)]
139#[macro_export]
140macro_rules! syscall {
141 (break $e:expr) => {
142 loop {
143 match $crate::syscall!($e) {
144 Ok(fd) => break ::std::task::Poll::Ready(Ok(fd as usize)),
145 Err(e) if e.kind() == ::std::io::ErrorKind::WouldBlock || e.raw_os_error() == Some(::libc::EINPROGRESS)
146 => break ::std::task::Poll::Pending,
147 Err(e) if e.kind() == ::std::io::ErrorKind::Interrupted => {},
148 Err(e) => break ::std::task::Poll::Ready(Err(e)),
149 }
150 }
151 };
152 ($e:expr, $f:ident($fd:expr)) => {
153 match $crate::syscall!(break $e) {
154 ::std::task::Poll::Pending => Ok($crate::sys::Decision::$f($fd)),
155 ::std::task::Poll::Ready(Ok(res)) => Ok($crate::sys::Decision::Completed(res)),
156 ::std::task::Poll::Ready(Err(e)) => Err(e),
157 }
158 };
159 ($e:expr) => {{
160 #[allow(unused_unsafe)]
161 let res = unsafe { $e };
162 if res == -1 {
163 Err(::std::io::Error::last_os_error())
164 } else {
165 Ok(res)
166 }
167 }};
168}
169
170/// Execute a future with custom `block_on` method and wait for result
171pub(super) fn block_on<F, R>(run: &dyn Runner, fut: F) -> Result<R, Box<dyn Any + Send>>
172where
173 F: Future<Output = R> + 'static,
174 R: 'static,
175{
176 // run loop
177 let result = Rc::new(RefCell::new(None));
178 let result_inner = result.clone();
179
180 ntex_error::set_backtrace_start_alt("ntex/ntex-rt/src/driver.rs", 0);
181 // finalizes the thread's arbiter once its event loop has exited,
182 // including when the runner returns an error or a shutdown callback panics
183 let finalize = FinalizeArbiter;
184 let fut = Box::pin(async move {
185 let r = fut.await;
186 *result_inner.borrow_mut() = Some(r);
187 });
188 // a panic escaping the runner is returned like a runner error, so the
189 // caller cleans up the same way for unwinding and catching runners
190 let res =
191 panic::catch_unwind(panic::AssertUnwindSafe(|| run.block_on(fut))).unwrap_or_else(Err);
192 if let Err(e) = res {
193 // the event loop panicked, a panic of `on_stop` aborts the process
194 crate::abort_on_panic(|| drop(finalize));
195 return Err(e);
196 }
197
198 crate::arbiter::run_shutdown_callbacks();
199 unsafe {
200 crate::remove_all_items();
201 }
202 Ok(result.borrow_mut().take().unwrap())
203}
204
205struct FinalizeArbiter;
206
207impl Drop for FinalizeArbiter {
208 fn drop(&mut self) {
209 if let Some(arb) = crate::Arbiter::try_current() {
210 arb.finalize();
211 }
212 }
213}
214
215pub(super) fn block_on_panic<F, R>(run: &dyn Runner, fut: F) -> R
216where
217 F: Future<Output = R> + 'static,
218 R: 'static,
219{
220 match block_on(run, fut) {
221 Ok(v) => v,
222 Err(e) => panic::resume_unwind(e),
223 }
224}
225
226#[cfg(test)]
227#[path = "../tests/unit/driver.rs"]
228mod tests;