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    /// Signal handling
20    signals: bool,
21    /// Panic handling
22    panics: bool,
23    /// Thread pool config
24    pool_limit: usize,
25    pool_recv_timeout: time::Duration,
26    /// testing flag
27    testing: bool,
28}
29
30impl Builder {
31    pub(super) fn new() -> Self {
32        Builder {
33            name: "ntex".into(),
34            stack_size: 0,
35            ping_interval: 2000,
36            ping_threshold: 1000,
37            signals: false,
38            panics: false,
39            testing: false,
40            pool_limit: 256,
41            pool_recv_timeout: time::Duration::from_mins(1),
42        }
43    }
44
45    #[must_use]
46    /// Sets the name of the System.
47    pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
48        self.name = name.as_ref().into();
49        self
50    }
51
52    #[doc(hidden)]
53    #[deprecated(since = "3.17.0")]
54    #[must_use]
55    /// Sets the option `stop_on_panic`
56    ///
57    /// It controls whether the System is stopped when an
58    /// uncaught panic is thrown from a worker thread.
59    ///
60    /// Defaults is set to false.
61    pub fn stop_on_panic(self, _: bool) -> Self {
62        self
63    }
64
65    #[must_use]
66    /// Enables or disables process signal handling.
67    ///
68    /// By default, signal handling is disabled.
69    pub fn signals(mut self, eanbled: bool) -> Self {
70        self.signals = eanbled;
71        self
72    }
73
74    #[must_use]
75    /// Enables panic handling.
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    /// By default, panic handling is disabled.
81    pub fn panic_handling(mut self, eanbled: bool) -> Self {
82        self.panics = eanbled;
83        self
84    }
85
86    #[doc(hidden)]
87    #[must_use]
88    /// Disable signal handling.
89    ///
90    /// By default, signal handling is disabled.
91    pub fn disable_signals(mut self) -> Self {
92        self.signals = false;
93        self
94    }
95
96    #[doc(hidden)]
97    #[must_use]
98    /// Enable signal handling.
99    ///
100    /// By default, signal handling is enabled.
101    pub fn enable_signals(mut self) -> Self {
102        self.signals = true;
103        self
104    }
105
106    #[must_use]
107    /// Sets the size of the stack (in bytes) for the new worker thread.
108    pub fn stack_size(mut self, size: usize) -> Self {
109        self.stack_size = size;
110        self
111    }
112
113    #[must_use]
114    /// Sets the ping interval for spawned arbiters.
115    ///
116    /// The interval is specified in milliseconds and defaults to 2,000.
117    /// Set it to zero to disable pings.
118    pub fn ping_interval(mut self, interval: usize) -> Self {
119        self.ping_interval = interval;
120        self
121    }
122
123    #[must_use]
124    /// Sets the ping response threshold.
125    ///
126    /// If a response takes too long, an attempt is made to create a backtrace
127    /// for the busy arbiter.
128    ///
129    /// The threshold is specified in milliseconds and defaults to 1,000.
130    pub fn ping_threshold(mut self, interval: usize) -> Self {
131        self.ping_threshold = interval;
132        self
133    }
134
135    #[must_use]
136    /// Sets the maximum number of blocking thread-pool workers.
137    ///
138    /// The default is 256.
139    pub fn thread_pool_limit(mut self, value: usize) -> Self {
140        self.pool_limit = value;
141        self
142    }
143
144    #[must_use]
145    /// Configures the system for testing.
146    ///
147    /// This disables signal and panic handling.
148    pub fn testing(mut self) -> Self {
149        self.testing = true;
150        self.signals = false;
151        self.panics = false;
152        self
153    }
154
155    #[must_use]
156    /// Sets how long an idle blocking worker waits before exiting.
157    ///
158    /// The default is 60 seconds.
159    pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
160    where
161        time::Duration: From<T>,
162    {
163        self.pool_recv_timeout = timeout.into();
164        self
165    }
166
167    /// Creates a system runner using the specified runtime runner.
168    ///
169    /// # Panics
170    ///
171    /// Panics if the runtime cannot be created.
172    pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
173        let config = SystemConfig {
174            name: self.name.clone(),
175            testing: self.testing,
176            stack_size: self.stack_size,
177            ping_interval: self.ping_interval,
178            ping_threshold: self.ping_threshold,
179            pool_limit: self.pool_limit,
180            pool_recv_timeout: self.pool_recv_timeout,
181            runner: Arc::new(runner),
182        };
183        self.build_with(config)
184    }
185
186    /// Creates a system runner from an existing system configuration.
187    ///
188    /// # Panics
189    ///
190    /// Panics if the runtime cannot be created.
191    pub fn build_with(self, config: SystemConfig) -> SystemRunner {
192        let runner = config.runner.clone();
193
194        // init system arbiter and run configuration method
195        SystemRunner {
196            config,
197            runner,
198            signals: self.signals,
199            panics: self.panics,
200            _t: PhantomData,
201        }
202    }
203}
204
205/// A configured system that has not yet started its event loop.
206#[must_use = "SystemRunner must be run"]
207pub struct SystemRunner {
208    config: SystemConfig,
209    runner: Arc<dyn Runner>,
210    signals: bool,
211    panics: bool,
212    _t: PhantomData<Rc<()>>,
213}
214
215impl SystemRunner {
216    /// Runs the event loop until [`System::stop()`] is called.
217    pub fn run_until_stop(self) -> io::Result<()> {
218        self.run(|| Ok(()))
219    }
220
221    /// Runs `f`, then drives the event loop until [`System::stop()`] is called.
222    pub fn run<F>(self, f: F) -> io::Result<()>
223    where
224        F: FnOnce() -> io::Result<()> + 'static,
225    {
226        log::info!("Starting {:?} system", self.config.name);
227
228        let SystemRunner {
229            config,
230            runner,
231            signals,
232            panics,
233            ..
234        } = self;
235
236        if panics {
237            signals::enable_panic_handling();
238        }
239
240        // run loop
241        crate::driver::block_on_panic(runner.as_ref(), async move {
242            let (system, stop) = System::start(config);
243            let _signals = SignalsGuard(system.clone());
244            if signals {
245                system.enable_signals();
246            }
247
248            f()?;
249
250            let result = stop.await;
251
252            match result {
253                Ok(code) => {
254                    if code != 0 {
255                        Err(io::Error::other(format!("Non-zero exit code: {code}")))
256                    } else {
257                        Ok(())
258                    }
259                }
260                Err(_) => Err(io::Error::other("Closed")),
261            }
262        })
263    }
264
265    #[allow(clippy::missing_panics_doc)]
266    /// Execute a future and wait for result.
267    pub fn block_on<F, R>(self, fut: F) -> R
268    where
269        F: Future<Output = R> + 'static,
270        R: 'static,
271    {
272        let SystemRunner {
273            config,
274            runner,
275            signals,
276            panics,
277            ..
278        } = self;
279
280        if panics {
281            signals::enable_panic_handling();
282        }
283
284        crate::driver::block_on_panic(runner.as_ref(), async move {
285            let (system, _) = System::start(config);
286            let _signals = SignalsGuard(system.clone());
287            if signals {
288                system.enable_signals();
289            }
290
291            let loc = current_location();
292            ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
293            fut.await
294        })
295    }
296
297    #[cfg(feature = "tokio")]
298    /// Execute a future and wait for result.
299    pub async fn run_local<F, R>(self, fut: F) -> R
300    where
301        F: Future<Output = R> + 'static,
302        R: 'static,
303    {
304        let SystemRunner { config, .. } = self;
305
306        // run loop
307        let result = tok_io::task::LocalSet::new()
308            .run_until(async move {
309                let (system, _) = System::start(config);
310                let _signals = SignalsGuard(system);
311
312                let loc = current_location();
313                ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
314                fut.await
315            })
316            .await;
317
318        crate::arbiter::run_shutdown_callbacks();
319        unsafe {
320            crate::remove_all_items();
321        }
322        result
323    }
324}
325
326/// Releases the signals for other systems when the system's future completes,
327/// fails or panics.
328struct SignalsGuard(System);
329
330impl Drop for SignalsGuard {
331    fn drop(&mut self) {
332        self.0.disable_signals();
333    }
334}
335
336#[track_caller]
337pub(crate) fn current_location() -> &'static panic::Location<'static> {
338    panic::Location::caller()
339}
340
341impl fmt::Debug for SystemRunner {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        f.debug_struct("SystemRunner")
344            .field("config", &self.config)
345            .finish()
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use std::{cell::Cell, cell::RefCell, time::Duration};
352
353    use super::*;
354    use crate::{Arbiter, testing::TestRunner};
355
356    #[test]
357    #[allow(deprecated)]
358    fn builder_options() {
359        let builder = System::build()
360            .name("opts")
361            .stop_on_panic(true)
362            .signals(false)
363            .panic_handling(false)
364            .enable_signals()
365            .disable_signals()
366            .stack_size(2 * 1024 * 1024)
367            .ping_interval(0)
368            .ping_threshold(10)
369            .thread_pool_limit(1)
370            .thread_pool_recv_timeout(Duration::from_millis(100))
371            .testing();
372        assert!(format!("{builder:?}").contains("opts"));
373
374        let runner = builder.build(TestRunner);
375        assert!(format!("{runner:?}").contains("opts"));
376        runner.block_on(async {
377            let sys = System::current();
378            assert_eq!(sys.name(), "opts");
379            assert!(sys.testing());
380            assert!(!sys.signals());
381
382            // arbiter thread with configured stack size
383            let mut arb = Arbiter::new();
384            arb.stop();
385            arb.join().unwrap();
386
387            assert_eq!(sys.spawn_blocking(|| 1).await, Ok(1));
388        });
389    }
390
391    #[test]
392    fn run_exit_codes() {
393        // `f` error is returned
394        let err = System::new("test", TestRunner)
395            .run(|| Err(io::Error::other("init failed")))
396            .unwrap_err();
397        assert_eq!(err.to_string(), "init failed");
398
399        System::new("test", TestRunner)
400            .run(|| {
401                System::current().stop();
402                Ok(())
403            })
404            .unwrap();
405
406        // stopping the system stops arbiters and runs shutdown callbacks
407        let called = Rc::new(Cell::new(false));
408        let arb = Rc::new(RefCell::new(None));
409        let (called2, arb2) = (called.clone(), arb.clone());
410        let err = System::new("test", TestRunner)
411            .run(move || {
412                Arbiter::on_shutdown(move || called2.set(true));
413                *arb2.borrow_mut() = Some(Arbiter::new());
414                System::current().stop_with_code(3);
415                Ok(())
416            })
417            .unwrap_err();
418        assert_eq!(err.to_string(), "Non-zero exit code: 3");
419        assert!(called.get());
420
421        let mut arb = arb.borrow_mut().take().unwrap();
422        arb.join().unwrap();
423        assert!(!arb.is_running());
424    }
425}