Skip to main content

ntex_rt/
builder.rs

1use std::{fmt, future::Future, io, marker::PhantomData, panic, rc::Rc, sync::Arc, time};
2
3use crate::{driver::Runner, signals, system::System, system::SystemConfig};
4
5#[derive(Debug, Clone)]
6/// Builder for an ntex runtime system.
7///
8/// Obtain a builder from [`System::build`]. Use [`build`](Self::build) to create
9/// a [`SystemRunner`], then run the event loop or block on a future.
10pub struct Builder {
11    /// Name of the System. Defaults to "ntex" if unset.
12    name: String,
13    /// New thread stack size
14    stack_size: usize,
15    /// Arbiters ping interval
16    ping_interval: usize,
17    /// Arbiter ping response threshold
18    ping_threshold: usize,
19    /// Arbiter latency sampling interval
20    latency_interval: usize,
21    /// Signal handling
22    signals: bool,
23    /// Panic handling
24    panics: bool,
25    /// Thread pool config
26    pool_limit: usize,
27    pool_recv_timeout: time::Duration,
28    /// testing flag
29    testing: bool,
30}
31
32impl Builder {
33    pub(super) fn new() -> Self {
34        Builder {
35            name: "ntex".into(),
36            stack_size: 0,
37            ping_interval: 2000,
38            ping_threshold: 1000,
39            latency_interval: 100,
40            signals: false,
41            panics: false,
42            testing: false,
43            pool_limit: 256,
44            pool_recv_timeout: time::Duration::from_mins(1),
45        }
46    }
47
48    #[must_use]
49    /// Sets the system name, used when naming its threads.
50    ///
51    /// The default is `"ntex"`.
52    pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
53        self.name = name.as_ref().into();
54        self
55    }
56
57    #[must_use]
58    /// Enables or disables process signal handling.
59    ///
60    /// By default, signal handling is disabled. At startup the system attempts
61    /// to acquire process-wide ownership; another system's existing registration
62    /// is not replaced. See [`System::enable_signals`].
63    pub fn signals(mut self, eanbled: bool) -> Self {
64        self.signals = eanbled;
65        self
66    }
67
68    #[must_use]
69    /// Enables or disables installation of the process-wide panic hook at startup.
70    ///
71    /// When panic handling is enabled, the application can receive
72    /// `Signal::Panic(PanicSource::App(..))` signals while signal handling
73    /// is enabled. The previously installed panic hook is still called.
74    /// The hook is installed once and remains installed for the process lifetime;
75    /// `false` does not remove a hook installed earlier. By default, installation
76    /// is disabled.
77    pub fn panic_handling(mut self, eanbled: bool) -> Self {
78        self.panics = eanbled;
79        self
80    }
81
82    #[doc(hidden)]
83    #[must_use]
84    /// Disable signal handling.
85    ///
86    /// By default, signal handling is disabled.
87    pub fn disable_signals(mut self) -> Self {
88        self.signals = false;
89        self
90    }
91
92    #[doc(hidden)]
93    #[must_use]
94    /// Enable signal handling.
95    ///
96    /// By default, signal handling is disabled.
97    pub fn enable_signals(mut self) -> Self {
98        self.signals = true;
99        self
100    }
101
102    #[must_use]
103    /// Sets the stack size, in bytes, for newly spawned arbiter threads.
104    ///
105    /// The default is zero, which uses the standard library's thread stack size.
106    /// This does not change the primary arbiter's or blocking pool workers' stacks.
107    pub fn stack_size(mut self, size: usize) -> Self {
108        self.stack_size = size;
109        self
110    }
111
112    #[must_use]
113    /// Sets the ping interval for all registered arbiters, including the primary.
114    ///
115    /// The interval is specified in milliseconds and defaults to 2,000.
116    /// Set it to zero to disable pings.
117    pub fn ping_interval(mut self, interval: usize) -> Self {
118        self.ping_interval = interval;
119        self
120    }
121
122    #[must_use]
123    /// Sets the arbiter stall threshold.
124    ///
125    /// A watchdog thread marks an arbiter as stalled when it records no
126    /// latency sample for longer than the
127    /// [`latency_interval`](Self::latency_interval) plus this threshold, see
128    /// [`LatencyMetrics::stalls`](crate::metrics::LatencyMetrics::stalls). On Linux and
129    /// Windows an attempt is made to create a backtrace for the stalled arbiter.
130    ///
131    /// The threshold is specified in milliseconds and defaults to 1,000.
132    pub fn ping_threshold(mut self, interval: usize) -> Self {
133        self.ping_threshold = interval;
134        self
135    }
136
137    #[must_use]
138    /// Sets the scheduling latency sampling interval for arbiters.
139    ///
140    /// Each arbiter runs a task that sleeps for the interval and records how
141    /// late it wakes up, see [`Arbiter::latency`](crate::Arbiter::latency).
142    ///
143    /// The interval is specified in milliseconds and defaults to 100.
144    /// Set it to zero to disable sampling and stall detection.
145    pub fn latency_interval(mut self, interval: usize) -> Self {
146        self.latency_interval = interval;
147        self
148    }
149
150    #[must_use]
151    /// Sets the maximum number of blocking thread-pool workers.
152    ///
153    /// The default is 256. Zero is treated as one; excess work is queued.
154    pub fn thread_pool_limit(mut self, value: usize) -> Self {
155        self.pool_limit = value;
156        self
157    }
158
159    #[must_use]
160    /// Configures the system for testing.
161    ///
162    /// Sets the testing flag and disables signal and panic handling on this
163    /// builder. Subsequent calls can enable either again.
164    pub fn testing(mut self) -> Self {
165        self.testing = true;
166        self.signals = false;
167        self.panics = false;
168        self
169    }
170
171    #[must_use]
172    /// Sets how long an idle blocking worker waits before exiting.
173    ///
174    /// The default is 60 seconds.
175    pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
176    where
177        time::Duration: From<T>,
178    {
179        self.pool_recv_timeout = timeout.into();
180        self
181    }
182
183    /// Creates a system runner using the specified runtime runner.
184    ///
185    /// This stores configuration only; the system starts when the returned
186    /// [`SystemRunner`] is driven.
187    pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
188        let config = SystemConfig {
189            name: self.name.clone(),
190            testing: self.testing,
191            stack_size: self.stack_size,
192            ping_interval: self.ping_interval,
193            ping_threshold: self.ping_threshold,
194            latency_interval: self.latency_interval,
195            pool_limit: self.pool_limit,
196            pool_recv_timeout: self.pool_recv_timeout,
197            runner: Arc::new(runner),
198        };
199        self.build_with(config)
200    }
201
202    /// Creates a system runner from an existing system configuration.
203    ///
204    /// The supplied configuration determines the name, runtime runner, and all
205    /// other system settings. Only this builder's signal and panic-handling
206    /// settings are used. No event loop is started by this method.
207    pub fn build_with(self, config: SystemConfig) -> SystemRunner {
208        let runner = config.runner.clone();
209
210        // init system arbiter and run configuration method
211        SystemRunner {
212            config,
213            runner,
214            signals: self.signals,
215            panics: self.panics,
216            _t: PhantomData,
217        }
218    }
219}
220
221/// A configured system that has not yet started its event loop.
222///
223/// Driving it installs a system and primary arbiter on the current thread.
224/// This value is neither `Send` nor `Sync`.
225#[must_use = "SystemRunner must be run"]
226pub struct SystemRunner {
227    config: SystemConfig,
228    runner: Arc<dyn Runner>,
229    signals: bool,
230    panics: bool,
231    _t: PhantomData<Rc<()>>,
232}
233
234impl SystemRunner {
235    /// Runs the event loop until [`System::stop()`] is called.
236    ///
237    /// Equivalent to [`run`](Self::run) with an initialization callback that
238    /// returns `Ok(())`, including its error and panic behavior.
239    pub fn run_until_stop(self) -> io::Result<()> {
240        self.run(|| Ok(()))
241    }
242
243    /// Runs `f`, then drives the event loop until [`System::stop()`] is called.
244    ///
245    /// The callback runs inside the event loop with the system and its primary
246    /// arbiter installed. On exit, the system requests its arbiters to stop.
247    ///
248    /// # Errors
249    ///
250    /// Returns the initialization error from `f`, or an error if the system
251    /// stops with a nonzero exit code or its stop channel closes unexpectedly.
252    ///
253    /// # Panics
254    ///
255    /// Resumes panics from initialization or the runtime runner after cleanup.
256    /// Also panics if the runner returns successfully without completing its
257    /// supplied future.
258    pub fn run<F>(self, f: F) -> io::Result<()>
259    where
260        F: FnOnce() -> io::Result<()> + 'static,
261    {
262        log::info!("Starting {:?} system", self.config.name);
263
264        let SystemRunner {
265            config,
266            runner,
267            signals,
268            panics,
269            ..
270        } = self;
271
272        if panics {
273            signals::enable_panic_handling();
274        }
275
276        // run loop
277        crate::driver::block_on_panic(runner.as_ref(), async move {
278            let (system, stop) = System::start(config);
279            let _guard = SystemGuard(system.clone());
280            if signals {
281                system.enable_signals();
282            }
283
284            f()?;
285
286            let result = stop.await;
287
288            match result {
289                Ok(code) => {
290                    if code != 0 {
291                        Err(io::Error::other(format!("Non-zero exit code: {code}")))
292                    } else {
293                        Ok(())
294                    }
295                }
296                Err(_) => Err(io::Error::other("Closed")),
297            }
298        })
299    }
300
301    #[allow(clippy::missing_panics_doc)]
302    /// Runs the system until `fut` completes and returns its output.
303    ///
304    /// The future runs with the system and primary arbiter installed. Completion
305    /// requests all system arbiters to stop. Unlike [`run`](Self::run), this
306    /// waits for the future, not a stop request, and does not check the system's
307    /// exit code.
308    ///
309    /// # Panics
310    ///
311    /// Resumes panics from the future or runtime runner after cleanup. Also
312    /// panics if the runner returns without completing its supplied future.
313    pub fn block_on<F, R>(self, fut: F) -> R
314    where
315        F: Future<Output = R> + 'static,
316        R: 'static,
317    {
318        let SystemRunner {
319            config,
320            runner,
321            signals,
322            panics,
323            ..
324        } = self;
325
326        if panics {
327            signals::enable_panic_handling();
328        }
329
330        crate::driver::block_on_panic(runner.as_ref(), async move {
331            let (system, _) = System::start(config);
332            let _guard = SystemGuard(system.clone());
333            if signals {
334                system.enable_signals();
335            }
336
337            let loc = current_location();
338            ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
339            fut.await
340        })
341    }
342
343    #[cfg(feature = "tokio")]
344    /// Runs the system's future in a local task set on an existing Tokio runtime.
345    ///
346    /// Does not invoke the configured [`Runner`] or apply the builder's signal
347    /// and panic-handling settings. The system and primary arbiter are installed
348    /// while the future runs, and shutdown callbacks are run on normal completion.
349    /// Like [`block_on`](Self::block_on), this waits for the future rather than
350    /// a stop request.
351    ///
352    /// # Panics
353    ///
354    /// Panics if used without a compatible Tokio runtime context. Panics from
355    /// the future propagate to the caller.
356    pub async fn run_local<F, R>(self, fut: F) -> R
357    where
358        F: Future<Output = R> + 'static,
359        R: 'static,
360    {
361        let SystemRunner { config, .. } = self;
362
363        // run loop
364        let result = tok_io::task::LocalSet::new()
365            .run_until(async move {
366                let (system, _) = System::start(config);
367                let _guard = SystemGuard(system);
368
369                let loc = current_location();
370                ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
371                fut.await
372            })
373            .await;
374
375        crate::arbiter::run_shutdown_callbacks();
376        unsafe {
377            crate::remove_all_items();
378        }
379        if let Some(arb) = crate::Arbiter::try_current() {
380            arb.finalize();
381        }
382        result
383    }
384}
385
386/// Stops the system's arbiters and releases the signals for other systems when
387/// the system's future completes, fails, panics or is dropped.
388struct SystemGuard(System);
389
390impl Drop for SystemGuard {
391    fn drop(&mut self) {
392        self.0.disable_signals();
393
394        if std::thread::panicking() {
395            // some executors (compio) abort the process if a task is woken
396            // while the thread unwinds, stop arbiters from another thread
397            let list = self.0.take_arbiters();
398            if !list.is_empty() {
399                let stopped = std::thread::Builder::new()
400                    .spawn(move || list.into_iter().for_each(|arb| arb.stop()))
401                    .map(std::thread::JoinHandle::join);
402                if let Ok(Err(payload)) = stopped {
403                    std::mem::forget(payload);
404                    crate::abort_on_second_panic();
405                }
406            }
407        } else {
408            self.0.stop_arbiters();
409        }
410    }
411}
412
413#[track_caller]
414pub(crate) fn current_location() -> &'static panic::Location<'static> {
415    panic::Location::caller()
416}
417
418impl fmt::Debug for SystemRunner {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        f.debug_struct("SystemRunner")
421            .field("config", &self.config)
422            .finish()
423    }
424}
425
426#[cfg(test)]
427#[path = "../tests/unit/builder.rs"]
428mod tests;