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;