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_secs(60),
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(PanicReason::Panic(..))` signals.
79    /// By default, panic handling 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 enabled.
100    pub fn enable_signals(mut self) -> Self {
101        self.signals = true;
102        self
103    }
104
105    #[must_use]
106    /// Sets the size of the stack (in bytes) for the new worker thread.
107    pub fn stack_size(mut self, size: usize) -> Self {
108        self.stack_size = size;
109        self
110    }
111
112    #[must_use]
113    /// Sets the ping interval for spawned arbiters.
114    ///
115    /// The interval is specified in milliseconds and defaults to 2,000.
116    /// Set it to zero to disable pings.
117    pub fn ping_interval(mut self, interval: usize) -> Self {
118        self.ping_interval = interval;
119        self
120    }
121
122    #[must_use]
123    /// Sets the ping response threshold.
124    ///
125    /// If a response takes too long, an attempt is made to create a backtrace
126    /// for the busy arbiter.
127    ///
128    /// The threshold is specified in milliseconds and defaults to 1,000.
129    pub fn ping_threshold(mut self, interval: usize) -> Self {
130        self.ping_threshold = interval;
131        self
132    }
133
134    #[must_use]
135    /// Sets the maximum number of blocking thread-pool workers.
136    ///
137    /// The default is 256.
138    pub fn thread_pool_limit(mut self, value: usize) -> Self {
139        self.pool_limit = value;
140        self
141    }
142
143    #[must_use]
144    /// Configures the system for testing.
145    ///
146    /// This disables signal and panic handling.
147    pub fn testing(mut self) -> Self {
148        self.testing = true;
149        self.signals = false;
150        self.panics = false;
151        self
152    }
153
154    #[must_use]
155    /// Sets how long an idle blocking worker waits before exiting.
156    ///
157    /// The default is 60 seconds.
158    pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
159    where
160        time::Duration: From<T>,
161    {
162        self.pool_recv_timeout = timeout.into();
163        self
164    }
165
166    /// Creates a system runner using the specified runtime runner.
167    ///
168    /// # Panics
169    ///
170    /// Panics if the runtime cannot be created.
171    pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
172        let config = SystemConfig {
173            name: self.name.clone(),
174            testing: self.testing,
175            stack_size: self.stack_size,
176            ping_interval: self.ping_interval,
177            ping_threshold: self.ping_threshold,
178            pool_limit: self.pool_limit,
179            pool_recv_timeout: self.pool_recv_timeout,
180            runner: Arc::new(runner),
181        };
182        self.build_with(config)
183    }
184
185    /// Creates a system runner from an existing system configuration.
186    ///
187    /// # Panics
188    ///
189    /// Panics if the runtime cannot be created.
190    pub fn build_with(self, config: SystemConfig) -> SystemRunner {
191        let runner = config.runner.clone();
192
193        // init system arbiter and run configuration method
194        SystemRunner {
195            config,
196            runner,
197            signals: self.signals,
198            panics: self.panics,
199            _t: PhantomData,
200        }
201    }
202}
203
204/// A configured system that has not yet started its event loop.
205#[must_use = "SystemRunner must be run"]
206pub struct SystemRunner {
207    config: SystemConfig,
208    runner: Arc<dyn Runner>,
209    signals: bool,
210    panics: bool,
211    _t: PhantomData<Rc<()>>,
212}
213
214impl SystemRunner {
215    /// Runs the event loop until [`System::stop()`] is called.
216    pub fn run_until_stop(self) -> io::Result<()> {
217        self.run(|| Ok(()))
218    }
219
220    /// Runs `f`, then drives the event loop until [`System::stop()`] is called.
221    pub fn run<F>(self, f: F) -> io::Result<()>
222    where
223        F: FnOnce() -> io::Result<()> + 'static,
224    {
225        log::info!("Starting {:?} system", self.config.name);
226
227        let SystemRunner {
228            config,
229            runner,
230            signals,
231            panics,
232            ..
233        } = self;
234
235        if panics {
236            signals::enable_panic_handling();
237        }
238
239        // run loop
240        crate::driver::block_on_panic(runner.as_ref(), async move {
241            let (system, stop) = System::start(config);
242            if signals {
243                system.enable_signals();
244            }
245
246            f()?;
247
248            match stop.await {
249                Ok(code) => {
250                    if code != 0 {
251                        Err(io::Error::other(format!("Non-zero exit code: {code}")))
252                    } else {
253                        Ok(())
254                    }
255                }
256                Err(_) => Err(io::Error::other("Closed")),
257            }
258        })
259    }
260
261    #[allow(clippy::missing_panics_doc)]
262    /// Execute a future and wait for result.
263    pub fn block_on<F, R>(self, fut: F) -> R
264    where
265        F: Future<Output = R> + 'static,
266        R: 'static,
267    {
268        let SystemRunner {
269            config,
270            runner,
271            signals,
272            panics,
273            ..
274        } = self;
275
276        if panics {
277            signals::enable_panic_handling();
278        }
279
280        crate::driver::block_on_panic(runner.as_ref(), async move {
281            let (system, _) = System::start(config);
282            if signals {
283                system.enable_signals();
284            }
285
286            let loc = current_location();
287            ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
288            fut.await
289        })
290    }
291
292    #[cfg(feature = "tokio")]
293    /// Execute a future and wait for result.
294    pub async fn run_local<F, R>(self, fut: F) -> R
295    where
296        F: Future<Output = R> + 'static,
297        R: 'static,
298    {
299        let SystemRunner { config, .. } = self;
300
301        // run loop
302        let result = tok_io::task::LocalSet::new()
303            .run_until(async move {
304                _ = System::start(config);
305
306                let loc = current_location();
307                ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
308                fut.await
309            })
310            .await;
311
312        unsafe {
313            crate::remove_all_items();
314        }
315        result
316    }
317}
318
319#[track_caller]
320pub(crate) fn current_location() -> &'static panic::Location<'static> {
321    panic::Location::caller()
322}
323
324impl fmt::Debug for SystemRunner {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        f.debug_struct("SystemRunner")
327            .field("config", &self.config)
328            .finish()
329    }
330}