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