Skip to main content

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;