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