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