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 _ = std::thread::Builder::new()
350                    .spawn(move || list.into_iter().for_each(|arb| arb.stop()))
351                    .map(std::thread::JoinHandle::join);
352            }
353        } else {
354            self.0.stop_arbiters();
355        }
356    }
357}
358
359#[track_caller]
360pub(crate) fn current_location() -> &'static panic::Location<'static> {
361    panic::Location::caller()
362}
363
364impl fmt::Debug for SystemRunner {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        f.debug_struct("SystemRunner")
367            .field("config", &self.config)
368            .finish()
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use std::sync::atomic::{AtomicUsize, Ordering};
375    use std::{cell::Cell, cell::RefCell, time::Duration};
376
377    use super::*;
378    use crate::{Arbiter, testing::TestRunner};
379
380    #[test]
381    fn builder_options() {
382        let builder = System::build()
383            .name("opts")
384            .signals(false)
385            .panic_handling(false)
386            .enable_signals()
387            .disable_signals()
388            .stack_size(2 * 1024 * 1024)
389            .ping_interval(0)
390            .ping_threshold(10)
391            .thread_pool_limit(1)
392            .thread_pool_recv_timeout(Duration::from_millis(100))
393            .testing();
394        assert!(format!("{builder:?}").contains("opts"));
395
396        let runner = builder.build(TestRunner);
397        assert!(format!("{runner:?}").contains("opts"));
398        runner.block_on(async {
399            let sys = System::current();
400            assert_eq!(sys.name(), "opts");
401            assert!(sys.testing());
402            assert!(!sys.signals());
403
404            // arbiter thread with configured stack size
405            let mut arb = Arbiter::new();
406            arb.stop();
407            arb.join().unwrap();
408
409            assert_eq!(sys.spawn_blocking(|| 1).await, Ok(1));
410        });
411    }
412
413    #[test]
414    fn run_exit_codes() {
415        // `f` error is returned
416        let err = System::new("test", TestRunner)
417            .run(|| Err(io::Error::other("init failed")))
418            .unwrap_err();
419        assert_eq!(err.to_string(), "init failed");
420
421        System::new("test", TestRunner)
422            .run(|| {
423                System::current().stop();
424                Ok(())
425            })
426            .unwrap();
427
428        // stopping the system stops arbiters and runs shutdown callbacks
429        let called = Rc::new(Cell::new(false));
430        let arb = Rc::new(RefCell::new(None));
431        let (called2, arb2) = (called.clone(), arb.clone());
432        let err = System::new("test", TestRunner)
433            .run(move || {
434                Arbiter::on_shutdown(move || called2.set(true));
435                *arb2.borrow_mut() = Some(Arbiter::new());
436                System::current().stop_with_code(3);
437                Ok(())
438            })
439            .unwrap_err();
440        assert_eq!(err.to_string(), "Non-zero exit code: 3");
441        assert!(called.get());
442
443        let mut arb = arb.borrow_mut().take().unwrap();
444        arb.join().unwrap();
445        assert!(!arb.is_running());
446    }
447
448    #[test]
449    fn exit_stops_child_arbiters() {
450        fn assert_stopped(arb: &Rc<RefCell<Option<Arbiter>>>) {
451            let arb = arb.borrow_mut().take().unwrap();
452            let mut arb2 = arb.clone();
453            let (tx, rx) = std::sync::mpsc::channel();
454            std::thread::spawn(move || {
455                let _ = tx.send(arb2.join().is_ok());
456            });
457            let stopped = rx.recv_timeout(Duration::from_secs(5));
458            arb.stop();
459            assert_eq!(stopped, Ok(true), "child arbiter is still running");
460        }
461
462        // initialization error
463        let arb = Rc::new(RefCell::new(None));
464        let arb2 = arb.clone();
465        let err = System::new("test", TestRunner)
466            .run(move || {
467                *arb2.borrow_mut() = Some(Arbiter::new());
468                Err(io::Error::other("init failed"))
469            })
470            .unwrap_err();
471        assert_eq!(err.to_string(), "init failed");
472        assert_stopped(&arb);
473
474        // root future completion
475        let arb2 = arb.clone();
476        System::new("test", TestRunner).block_on(async move {
477            *arb2.borrow_mut() = Some(Arbiter::new());
478        });
479        assert_stopped(&arb);
480
481        // root future panic
482        let arb2 = arb.clone();
483        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
484            System::new("test", TestRunner).block_on(async move {
485                *arb2.borrow_mut() = Some(Arbiter::new());
486                panic!("root future panic");
487            });
488        }));
489        assert!(result.is_err());
490        assert_stopped(&arb);
491    }
492
493    #[test]
494    fn primary_arbiter_finalized() {
495        fn with_on_stop(calls: &Arc<AtomicUsize>) -> Arbiter {
496            let calls = calls.clone();
497            Arbiter::current().on_stop(move || {
498                calls.fetch_add(1, Ordering::Relaxed);
499            })
500        }
501
502        // root future completion
503        let calls = Arc::new(AtomicUsize::new(0));
504        let calls2 = calls.clone();
505        let arb = System::new("test", TestRunner).block_on(async move { with_on_stop(&calls2) });
506        assert!(!arb.is_running());
507        assert_eq!(calls.load(Ordering::Relaxed), 1);
508
509        // root future panic
510        let calls = Arc::new(AtomicUsize::new(0));
511        let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
512        let slot2 = slot.clone();
513        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
514            System::new("test", TestRunner).block_on(async move {
515                *slot2.borrow_mut() = Some(with_on_stop(&calls2));
516                panic!("root future panic");
517            });
518        }));
519        assert!(result.is_err());
520        assert!(!slot.borrow().as_ref().unwrap().is_running());
521        assert_eq!(calls.load(Ordering::Relaxed), 1);
522
523        // `run`
524        let calls = Arc::new(AtomicUsize::new(0));
525        let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
526        let slot2 = slot.clone();
527        System::new("test", TestRunner)
528            .run(move || {
529                *slot2.borrow_mut() = Some(with_on_stop(&calls2));
530                System::current().stop();
531                Ok(())
532            })
533            .unwrap();
534        assert!(!slot.borrow().as_ref().unwrap().is_running());
535        assert_eq!(calls.load(Ordering::Relaxed), 1);
536    }
537
538    #[cfg(feature = "tokio")]
539    #[test]
540    fn run_local_finalizes_primary_arbiter() {
541        let calls = Arc::new(AtomicUsize::new(0));
542        let calls2 = calls.clone();
543        let rt = tok_io::runtime::Builder::new_current_thread()
544            .enable_all()
545            .build()
546            .unwrap();
547        let arb = rt.block_on(System::new("test", TestRunner).run_local(async move {
548            Arbiter::current().on_stop(move || {
549                calls2.fetch_add(1, Ordering::Relaxed);
550            })
551        }));
552        assert!(!arb.is_running());
553        assert_eq!(calls.load(Ordering::Relaxed), 1);
554    }
555}