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/// Use [`build`](Self::build) to create a [`SystemRunner`], then run the event
9/// 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 name of the System.
50    pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
51        self.name = name.as_ref().into();
52        self
53    }
54
55    #[must_use]
56    /// Enables or disables process signal handling.
57    ///
58    /// By default, signal handling is disabled.
59    pub fn signals(mut self, eanbled: bool) -> Self {
60        self.signals = eanbled;
61        self
62    }
63
64    #[must_use]
65    /// Enables panic handling.
66    ///
67    /// When panic handling is enabled, the application can receive
68    /// `Signal::Panic(PanicSource::App(..))` signals while signal handling
69    /// is enabled. The previously installed panic hook is still called.
70    /// By default, panic handling is disabled.
71    pub fn panic_handling(mut self, eanbled: bool) -> Self {
72        self.panics = eanbled;
73        self
74    }
75
76    #[doc(hidden)]
77    #[must_use]
78    /// Disable signal handling.
79    ///
80    /// By default, signal handling is disabled.
81    pub fn disable_signals(mut self) -> Self {
82        self.signals = false;
83        self
84    }
85
86    #[doc(hidden)]
87    #[must_use]
88    /// Enable signal handling.
89    ///
90    /// By default, signal handling is enabled.
91    pub fn enable_signals(mut self) -> Self {
92        self.signals = true;
93        self
94    }
95
96    #[must_use]
97    /// Sets the size of the stack (in bytes) for the new worker thread.
98    pub fn stack_size(mut self, size: usize) -> Self {
99        self.stack_size = size;
100        self
101    }
102
103    #[must_use]
104    /// Sets the ping interval for spawned arbiters.
105    ///
106    /// The interval is specified in milliseconds and defaults to 2,000.
107    /// Set it to zero to disable pings.
108    pub fn ping_interval(mut self, interval: usize) -> Self {
109        self.ping_interval = interval;
110        self
111    }
112
113    #[must_use]
114    /// Sets the arbiter stall threshold.
115    ///
116    /// A watchdog thread marks an arbiter as stalled when it records no
117    /// latency sample for longer than the
118    /// [`latency_interval`](Self::latency_interval) plus this threshold, see
119    /// [`LatencyMetrics::stalls`](crate::LatencyMetrics::stalls). On Linux and
120    /// Windows an attempt is made to create a backtrace for the stalled arbiter.
121    ///
122    /// The threshold is specified in milliseconds and defaults to 1,000.
123    pub fn ping_threshold(mut self, interval: usize) -> Self {
124        self.ping_threshold = interval;
125        self
126    }
127
128    #[must_use]
129    /// Sets the scheduling latency sampling interval for arbiters.
130    ///
131    /// Each arbiter runs a task that sleeps for the interval and records how
132    /// late it wakes up, see [`Arbiter::latency`](crate::Arbiter::latency).
133    ///
134    /// The interval is specified in milliseconds and defaults to 100.
135    /// Set it to zero to disable sampling and stall detection.
136    pub fn latency_interval(mut self, interval: usize) -> Self {
137        self.latency_interval = interval;
138        self
139    }
140
141    #[must_use]
142    /// Sets the maximum number of blocking thread-pool workers.
143    ///
144    /// The default is 256.
145    pub fn thread_pool_limit(mut self, value: usize) -> Self {
146        self.pool_limit = value;
147        self
148    }
149
150    #[must_use]
151    /// Configures the system for testing.
152    ///
153    /// This disables signal and panic handling.
154    pub fn testing(mut self) -> Self {
155        self.testing = true;
156        self.signals = false;
157        self.panics = false;
158        self
159    }
160
161    #[must_use]
162    /// Sets how long an idle blocking worker waits before exiting.
163    ///
164    /// The default is 60 seconds.
165    pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
166    where
167        time::Duration: From<T>,
168    {
169        self.pool_recv_timeout = timeout.into();
170        self
171    }
172
173    /// Creates a system runner using the specified runtime runner.
174    ///
175    /// # Panics
176    ///
177    /// Panics if the runtime cannot be created.
178    pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
179        let config = SystemConfig {
180            name: self.name.clone(),
181            testing: self.testing,
182            stack_size: self.stack_size,
183            ping_interval: self.ping_interval,
184            ping_threshold: self.ping_threshold,
185            latency_interval: self.latency_interval,
186            pool_limit: self.pool_limit,
187            pool_recv_timeout: self.pool_recv_timeout,
188            runner: Arc::new(runner),
189        };
190        self.build_with(config)
191    }
192
193    /// Creates a system runner from an existing system configuration.
194    ///
195    /// # Panics
196    ///
197    /// Panics if the runtime cannot be created.
198    pub fn build_with(self, config: SystemConfig) -> SystemRunner {
199        let runner = config.runner.clone();
200
201        // init system arbiter and run configuration method
202        SystemRunner {
203            config,
204            runner,
205            signals: self.signals,
206            panics: self.panics,
207            _t: PhantomData,
208        }
209    }
210}
211
212/// A configured system that has not yet started its event loop.
213#[must_use = "SystemRunner must be run"]
214pub struct SystemRunner {
215    config: SystemConfig,
216    runner: Arc<dyn Runner>,
217    signals: bool,
218    panics: bool,
219    _t: PhantomData<Rc<()>>,
220}
221
222impl SystemRunner {
223    /// Runs the event loop until [`System::stop()`] is called.
224    pub fn run_until_stop(self) -> io::Result<()> {
225        self.run(|| Ok(()))
226    }
227
228    /// Runs `f`, then drives the event loop until [`System::stop()`] is called.
229    pub fn run<F>(self, f: F) -> io::Result<()>
230    where
231        F: FnOnce() -> io::Result<()> + 'static,
232    {
233        log::info!("Starting {:?} system", self.config.name);
234
235        let SystemRunner {
236            config,
237            runner,
238            signals,
239            panics,
240            ..
241        } = self;
242
243        if panics {
244            signals::enable_panic_handling();
245        }
246
247        // run loop
248        crate::driver::block_on_panic(runner.as_ref(), async move {
249            let (system, stop) = System::start(config);
250            let _guard = SystemGuard(system.clone());
251            if signals {
252                system.enable_signals();
253            }
254
255            f()?;
256
257            let result = stop.await;
258
259            match result {
260                Ok(code) => {
261                    if code != 0 {
262                        Err(io::Error::other(format!("Non-zero exit code: {code}")))
263                    } else {
264                        Ok(())
265                    }
266                }
267                Err(_) => Err(io::Error::other("Closed")),
268            }
269        })
270    }
271
272    #[allow(clippy::missing_panics_doc)]
273    /// Execute a future and wait for result.
274    pub fn block_on<F, R>(self, fut: F) -> R
275    where
276        F: Future<Output = R> + 'static,
277        R: 'static,
278    {
279        let SystemRunner {
280            config,
281            runner,
282            signals,
283            panics,
284            ..
285        } = self;
286
287        if panics {
288            signals::enable_panic_handling();
289        }
290
291        crate::driver::block_on_panic(runner.as_ref(), async move {
292            let (system, _) = System::start(config);
293            let _guard = SystemGuard(system.clone());
294            if signals {
295                system.enable_signals();
296            }
297
298            let loc = current_location();
299            ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
300            fut.await
301        })
302    }
303
304    #[cfg(feature = "tokio")]
305    /// Execute a future and wait for result.
306    pub async fn run_local<F, R>(self, fut: F) -> R
307    where
308        F: Future<Output = R> + 'static,
309        R: 'static,
310    {
311        let SystemRunner { config, .. } = self;
312
313        // run loop
314        let result = tok_io::task::LocalSet::new()
315            .run_until(async move {
316                let (system, _) = System::start(config);
317                let _guard = SystemGuard(system);
318
319                let loc = current_location();
320                ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
321                fut.await
322            })
323            .await;
324
325        crate::arbiter::run_shutdown_callbacks();
326        unsafe {
327            crate::remove_all_items();
328        }
329        if let Some(arb) = crate::Arbiter::try_current() {
330            arb.finalize();
331        }
332        result
333    }
334}
335
336/// Stops the system's arbiters and releases the signals for other systems when
337/// the system's future completes, fails, panics or is dropped.
338struct SystemGuard(System);
339
340impl Drop for SystemGuard {
341    fn drop(&mut self) {
342        self.0.disable_signals();
343
344        if std::thread::panicking() {
345            // some executors (compio) abort the process if a task is woken
346            // while the thread unwinds, stop arbiters from another thread
347            let list = self.0.take_arbiters();
348            if !list.is_empty() {
349                let stopped = std::thread::Builder::new()
350                    .spawn(move || list.into_iter().for_each(|arb| arb.stop()))
351                    .map(std::thread::JoinHandle::join);
352                if let Ok(Err(payload)) = stopped {
353                    std::mem::forget(payload);
354                    crate::abort_on_second_panic();
355                }
356            }
357        } else {
358            self.0.stop_arbiters();
359        }
360    }
361}
362
363#[track_caller]
364pub(crate) fn current_location() -> &'static panic::Location<'static> {
365    panic::Location::caller()
366}
367
368impl fmt::Debug for SystemRunner {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        f.debug_struct("SystemRunner")
371            .field("config", &self.config)
372            .finish()
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use std::sync::atomic::{AtomicUsize, Ordering};
379    use std::{cell::Cell, cell::RefCell, time::Duration};
380
381    use super::*;
382    use crate::{Arbiter, testing::TestRunner};
383
384    #[test]
385    fn builder_options() {
386        let builder = System::build()
387            .name("opts")
388            .signals(false)
389            .panic_handling(false)
390            .enable_signals()
391            .disable_signals()
392            .stack_size(2 * 1024 * 1024)
393            .ping_interval(0)
394            .ping_threshold(10)
395            .thread_pool_limit(1)
396            .thread_pool_recv_timeout(Duration::from_millis(100))
397            .testing();
398        assert!(format!("{builder:?}").contains("opts"));
399
400        let runner = builder.build(TestRunner);
401        assert!(format!("{runner:?}").contains("opts"));
402        runner.block_on(async {
403            let sys = System::current();
404            assert_eq!(sys.name(), "opts");
405            assert!(sys.testing());
406            assert!(!sys.signals());
407
408            // arbiter thread with configured stack size
409            let mut arb = Arbiter::new();
410            arb.stop();
411            arb.join().unwrap();
412
413            assert_eq!(sys.spawn_blocking(|| 1).await, Ok(1));
414        });
415    }
416
417    #[test]
418    fn run_exit_codes() {
419        // `f` error is returned
420        let err = System::new("test", TestRunner)
421            .run(|| Err(io::Error::other("init failed")))
422            .unwrap_err();
423        assert_eq!(err.to_string(), "init failed");
424
425        System::new("test", TestRunner)
426            .run(|| {
427                System::current().stop();
428                Ok(())
429            })
430            .unwrap();
431
432        // stopping the system stops arbiters and runs shutdown callbacks
433        let called = Rc::new(Cell::new(false));
434        let arb = Rc::new(RefCell::new(None));
435        let (called2, arb2) = (called.clone(), arb.clone());
436        let err = System::new("test", TestRunner)
437            .run(move || {
438                Arbiter::on_shutdown(move || called2.set(true));
439                *arb2.borrow_mut() = Some(Arbiter::new());
440                System::current().stop_with_code(3);
441                Ok(())
442            })
443            .unwrap_err();
444        assert_eq!(err.to_string(), "Non-zero exit code: 3");
445        assert!(called.get());
446
447        let mut arb = arb.borrow_mut().take().unwrap();
448        arb.join().unwrap();
449        assert!(!arb.is_running());
450    }
451
452    #[test]
453    fn exit_stops_child_arbiters() {
454        fn assert_stopped(arb: &Rc<RefCell<Option<Arbiter>>>) {
455            let arb = arb.borrow_mut().take().unwrap();
456            let mut arb2 = arb.clone();
457            let (tx, rx) = std::sync::mpsc::channel();
458            std::thread::spawn(move || {
459                let _ = tx.send(arb2.join().is_ok());
460            });
461            let stopped = rx.recv_timeout(Duration::from_secs(5));
462            arb.stop();
463            assert_eq!(stopped, Ok(true), "child arbiter is still running");
464        }
465
466        // initialization error
467        let arb = Rc::new(RefCell::new(None));
468        let arb2 = arb.clone();
469        let err = System::new("test", TestRunner)
470            .run(move || {
471                *arb2.borrow_mut() = Some(Arbiter::new());
472                Err(io::Error::other("init failed"))
473            })
474            .unwrap_err();
475        assert_eq!(err.to_string(), "init failed");
476        assert_stopped(&arb);
477
478        // root future completion
479        let arb2 = arb.clone();
480        System::new("test", TestRunner).block_on(async move {
481            *arb2.borrow_mut() = Some(Arbiter::new());
482        });
483        assert_stopped(&arb);
484
485        // root future panic
486        let arb2 = arb.clone();
487        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
488            System::new("test", TestRunner).block_on(async move {
489                *arb2.borrow_mut() = Some(Arbiter::new());
490                panic!("root future panic");
491            });
492        }));
493        assert!(result.is_err());
494        assert_stopped(&arb);
495    }
496
497    #[test]
498    fn primary_arbiter_finalized() {
499        fn with_on_stop(calls: &Arc<AtomicUsize>) -> Arbiter {
500            let calls = calls.clone();
501            Arbiter::current().on_stop(move || {
502                calls.fetch_add(1, Ordering::Relaxed);
503            })
504        }
505
506        // root future completion
507        let calls = Arc::new(AtomicUsize::new(0));
508        let calls2 = calls.clone();
509        let arb = System::new("test", TestRunner).block_on(async move { with_on_stop(&calls2) });
510        assert!(!arb.is_running());
511        assert_eq!(calls.load(Ordering::Relaxed), 1);
512
513        // root future panic
514        let calls = Arc::new(AtomicUsize::new(0));
515        let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
516        let slot2 = slot.clone();
517        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
518            System::new("test", TestRunner).block_on(async move {
519                *slot2.borrow_mut() = Some(with_on_stop(&calls2));
520                panic!("root future panic");
521            });
522        }));
523        assert!(result.is_err());
524        assert!(!slot.borrow().as_ref().unwrap().is_running());
525        assert_eq!(calls.load(Ordering::Relaxed), 1);
526
527        // `run`
528        let calls = Arc::new(AtomicUsize::new(0));
529        let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
530        let slot2 = slot.clone();
531        System::new("test", TestRunner)
532            .run(move || {
533                *slot2.borrow_mut() = Some(with_on_stop(&calls2));
534                System::current().stop();
535                Ok(())
536            })
537            .unwrap();
538        assert!(!slot.borrow().as_ref().unwrap().is_running());
539        assert_eq!(calls.load(Ordering::Relaxed), 1);
540    }
541
542    #[cfg(feature = "tokio")]
543    #[test]
544    fn run_local_finalizes_primary_arbiter() {
545        let calls = Arc::new(AtomicUsize::new(0));
546        let calls2 = calls.clone();
547        let rt = tok_io::runtime::Builder::new_current_thread()
548            .enable_all()
549            .build()
550            .unwrap();
551        let arb = rt.block_on(System::new("test", TestRunner).run_local(async move {
552            Arbiter::current().on_stop(move || {
553                calls2.fetch_add(1, Ordering::Relaxed);
554            })
555        }));
556        assert!(!arb.is_running());
557        assert_eq!(calls.load(Ordering::Relaxed), 1);
558    }
559}