ntex_rt/builder.rs
1use std::{fmt, future::Future, io, marker::PhantomData, panic, rc::Rc, sync::Arc, time};
2
3use crate::{TaskCallbacks, 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.
10#[allow(clippy::struct_excessive_bools)]
11pub struct Builder {
12 /// Name of the System. Defaults to "ntex" if unset.
13 name: String,
14 /// New thread stack size
15 stack_size: usize,
16 /// Arbiters ping interval
17 ping_interval: usize,
18 /// Arbiter ping response threshold
19 ping_threshold: usize,
20 /// Arbiter latency sampling interval
21 latency_interval: usize,
22 /// Signal handling
23 signals: bool,
24 /// Fatal signal handling
25 fatal_signals: bool,
26 /// Panic handling
27 panics: bool,
28 /// Thread pool config
29 pool_limit: usize,
30 pool_recv_timeout: time::Duration,
31 /// Timer wheel resolution in milliseconds
32 timer_resolution: u8,
33 /// testing flag
34 testing: bool,
35 /// Task instrumentation callbacks
36 task_callbacks: Option<fn() -> TaskCallbacks>,
37}
38
39impl Builder {
40 pub(super) fn new() -> Self {
41 Builder {
42 name: "ntex".into(),
43 stack_size: 0,
44 ping_interval: 2000,
45 ping_threshold: 1000,
46 latency_interval: 100,
47 signals: false,
48 fatal_signals: false,
49 panics: false,
50 testing: false,
51 pool_limit: 256,
52 pool_recv_timeout: time::Duration::from_mins(1),
53 timer_resolution: 16,
54 task_callbacks: None,
55 }
56 }
57
58 #[must_use]
59 /// Sets the system name, used when naming its threads.
60 ///
61 /// The default is `"ntex"`.
62 pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
63 self.name = name.as_ref().into();
64 self
65 }
66
67 #[must_use]
68 /// Enables or disables process signal handling.
69 ///
70 /// By default, signal handling is disabled. At startup the system attempts
71 /// to acquire process-wide ownership; another system's existing registration
72 /// is not replaced. See [`System::enable_signals`].
73 pub fn signals(mut self, eanbled: bool) -> Self {
74 self.signals = eanbled;
75 self
76 }
77
78 #[must_use]
79 /// Enables or disables installation of the process-wide panic hook at startup.
80 ///
81 /// When panic handling is enabled, the application can receive
82 /// `Signal::Panic(PanicSource::App(..))` signals while signal handling
83 /// is enabled. The previously installed panic hook is still called.
84 /// The hook is installed once and remains installed for the process lifetime;
85 /// `false` does not remove a hook installed earlier. By default, installation
86 /// is disabled.
87 pub fn panic_handling(mut self, eanbled: bool) -> Self {
88 self.panics = eanbled;
89 self
90 }
91
92 #[must_use]
93 /// Enables or disables installation of the process-wide `SIGSEGV` and
94 /// `SIGABRT` handlers at startup.
95 ///
96 /// When fatal signal handling is enabled, the application can receive
97 /// best-effort `Signal::Panic(PanicSource::Sig(..))` signals while signal
98 /// handling is enabled. The process still terminates as it would without
99 /// the handlers. The handlers are installed once and remain installed for
100 /// the process lifetime; `false` does not remove handlers installed earlier.
101 /// Has no effect on non-Unix platforms. By default, installation is disabled.
102 pub fn fatal_signals(mut self, enabled: bool) -> Self {
103 self.fatal_signals = enabled;
104 self
105 }
106
107 #[doc(hidden)]
108 #[must_use]
109 /// Disable signal handling.
110 ///
111 /// By default, signal handling is disabled.
112 pub fn disable_signals(mut self) -> Self {
113 self.signals = false;
114 self
115 }
116
117 #[doc(hidden)]
118 #[must_use]
119 /// Enable signal handling.
120 ///
121 /// By default, signal handling is disabled.
122 pub fn enable_signals(mut self) -> Self {
123 self.signals = true;
124 self
125 }
126
127 #[must_use]
128 /// Sets the stack size, in bytes, for newly spawned arbiter threads.
129 ///
130 /// The default is zero, which uses the standard library's thread stack size.
131 /// This does not change the primary arbiter's or blocking pool workers' stacks.
132 pub fn stack_size(mut self, size: usize) -> Self {
133 self.stack_size = size;
134 self
135 }
136
137 #[must_use]
138 /// Sets the ping interval for all registered arbiters, including the primary.
139 ///
140 /// The interval is specified in milliseconds and defaults to 2,000.
141 /// Set it to zero to disable pings.
142 pub fn ping_interval(mut self, interval: usize) -> Self {
143 self.ping_interval = interval;
144 self
145 }
146
147 #[must_use]
148 /// Sets the arbiter stall threshold.
149 ///
150 /// A watchdog thread marks an arbiter as stalled when it records no
151 /// latency sample for longer than the
152 /// [`latency_interval`](Self::latency_interval) plus this threshold, see
153 /// [`LatencyMetrics::stalls`](crate::metrics::LatencyMetrics::stalls). On Linux and
154 /// Windows an attempt is made to create a backtrace for the stalled arbiter.
155 ///
156 /// The threshold is specified in milliseconds and defaults to 1,000.
157 pub fn ping_threshold(mut self, interval: usize) -> Self {
158 self.ping_threshold = interval;
159 self
160 }
161
162 #[must_use]
163 /// Sets the scheduling latency sampling interval for arbiters.
164 ///
165 /// Each arbiter runs a task that sleeps for the interval and records how
166 /// late it wakes up, see [`Arbiter::latency`](crate::Arbiter::latency).
167 ///
168 /// The interval is specified in milliseconds and defaults to 100.
169 /// Set it to zero to disable sampling and stall detection.
170 pub fn latency_interval(mut self, interval: usize) -> Self {
171 self.latency_interval = interval;
172 self
173 }
174
175 #[must_use]
176 /// Sets the maximum number of blocking thread-pool workers.
177 ///
178 /// The default is 256. Zero is treated as one; excess work is queued.
179 pub fn thread_pool_limit(mut self, value: usize) -> Self {
180 self.pool_limit = value;
181 self
182 }
183
184 #[must_use]
185 /// Sets the resolution of the timer wheel, in milliseconds.
186 ///
187 /// Timers of the `ntex` time utilities fire on ticks of this length, a
188 /// timer may fire up to one tick late. A finer resolution reduces the
189 /// latency of short timers but wakes the timer driver more often and
190 /// shrinks the range of each wheel level.
191 ///
192 /// The resolution must be a power of two between 1 and 16. The default
193 /// is 16. Each thread reads the setting when its first timer is armed
194 /// and keeps it until the system stops.
195 ///
196 /// # Panics
197 ///
198 /// Panics if `millis` is not a power of two between 1 and 16.
199 pub fn timer_resolution(mut self, millis: u8) -> Self {
200 assert!(
201 millis.is_power_of_two() && millis <= 16,
202 "timer resolution must be a power of two between 1 and 16 ms, got {millis}"
203 );
204 self.timer_resolution = millis;
205 self
206 }
207
208 #[must_use]
209 /// Sets a factory of callbacks for instrumenting tasks spawned on the
210 /// system's threads.
211 ///
212 /// The factory is called on the system thread and on each of its arbiter
213 /// threads when the thread joins the system, so it can set up per-thread
214 /// state for its callbacks. See [`TaskCallbacks`] for when they are
215 /// called. By default, no callbacks are set.
216 pub fn task_callbacks(mut self, factory: fn() -> TaskCallbacks) -> Self {
217 self.task_callbacks = Some(factory);
218 self
219 }
220
221 #[must_use]
222 /// Configures the system for testing.
223 ///
224 /// Sets the testing flag and disables signal, fatal signal, and panic
225 /// handling on this builder. Subsequent calls can enable them again.
226 pub fn testing(mut self) -> Self {
227 self.testing = true;
228 self.signals = false;
229 self.fatal_signals = false;
230 self.panics = false;
231 self
232 }
233
234 #[must_use]
235 /// Sets how long an idle blocking worker waits before exiting.
236 ///
237 /// The default is 60 seconds.
238 pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
239 where
240 time::Duration: From<T>,
241 {
242 self.pool_recv_timeout = timeout.into();
243 self
244 }
245
246 /// Creates a system runner using the specified runtime runner.
247 ///
248 /// This stores configuration only; the system starts when the returned
249 /// [`SystemRunner`] is driven.
250 pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
251 let config = SystemConfig {
252 name: self.name.clone(),
253 testing: self.testing,
254 stack_size: self.stack_size,
255 ping_interval: self.ping_interval,
256 ping_threshold: self.ping_threshold,
257 latency_interval: self.latency_interval,
258 pool_limit: self.pool_limit,
259 pool_recv_timeout: self.pool_recv_timeout,
260 timer_resolution: self.timer_resolution,
261 task_callbacks: self.task_callbacks,
262 runner: Arc::new(runner),
263 };
264 self.build_with(config)
265 }
266
267 /// Creates a system runner from an existing system configuration.
268 ///
269 /// The supplied configuration determines the name, runtime runner, and all
270 /// other system settings. Only this builder's signal, fatal signal, and
271 /// panic-handling settings are used. No event loop is started by this method.
272 pub fn build_with(self, config: SystemConfig) -> SystemRunner {
273 let runner = config.runner.clone();
274
275 // init system arbiter and run configuration method
276 SystemRunner {
277 config,
278 runner,
279 signals: self.signals,
280 fatal_signals: self.fatal_signals,
281 panics: self.panics,
282 _t: PhantomData,
283 }
284 }
285}
286
287/// A configured system that has not yet started its event loop.
288///
289/// Driving it installs a system and primary arbiter on the current thread.
290/// This value is neither `Send` nor `Sync`.
291#[must_use = "SystemRunner must be run"]
292pub struct SystemRunner {
293 config: SystemConfig,
294 runner: Arc<dyn Runner>,
295 signals: bool,
296 fatal_signals: bool,
297 panics: bool,
298 _t: PhantomData<Rc<()>>,
299}
300
301impl SystemRunner {
302 /// Runs the event loop until [`System::stop()`] is called.
303 ///
304 /// Equivalent to [`run`](Self::run) with an initialization callback that
305 /// returns `Ok(())`, including its error and panic behavior.
306 pub fn run_until_stop(self) -> io::Result<()> {
307 self.run(|| Ok(()))
308 }
309
310 /// Runs `f`, then drives the event loop until [`System::stop()`] is called.
311 ///
312 /// The callback runs inside the event loop with the system and its primary
313 /// arbiter installed. On exit, the system requests its arbiters to stop.
314 ///
315 /// # Errors
316 ///
317 /// Returns the initialization error from `f`, or an error if the system
318 /// stops with a nonzero exit code or its stop channel closes unexpectedly.
319 ///
320 /// # Panics
321 ///
322 /// Resumes panics from initialization or the runtime runner after cleanup.
323 /// Also panics if the runner returns successfully without completing its
324 /// supplied future.
325 pub fn run<F>(self, f: F) -> io::Result<()>
326 where
327 F: FnOnce() -> io::Result<()> + 'static,
328 {
329 log::info!("Starting {:?} system", self.config.name);
330
331 let SystemRunner {
332 config,
333 runner,
334 signals,
335 fatal_signals,
336 panics,
337 ..
338 } = self;
339
340 if fatal_signals {
341 signals::enable_fatal_signals();
342 }
343 if panics {
344 signals::enable_panic_handling();
345 }
346
347 // run loop
348 crate::driver::block_on_panic(runner.as_ref(), async move {
349 let (system, stop) = System::start(config);
350 let _guard = SystemGuard(system.clone());
351 if signals {
352 system.enable_signals();
353 }
354
355 f()?;
356
357 let result = stop.await;
358
359 match result {
360 Ok(code) => {
361 if code != 0 {
362 Err(io::Error::other(format!("Non-zero exit code: {code}")))
363 } else {
364 Ok(())
365 }
366 }
367 Err(_) => Err(io::Error::other("Closed")),
368 }
369 })
370 }
371
372 #[allow(clippy::missing_panics_doc)]
373 /// Runs the system until `fut` completes and returns its output.
374 ///
375 /// The future runs with the system and primary arbiter installed. Completion
376 /// requests all system arbiters to stop. Unlike [`run`](Self::run), this
377 /// waits for the future, not a stop request, and does not check the system's
378 /// exit code.
379 ///
380 /// # Panics
381 ///
382 /// Resumes panics from the future or runtime runner after cleanup. Also
383 /// panics if the runner returns without completing its supplied future.
384 pub fn block_on<F, R>(self, fut: F) -> R
385 where
386 F: Future<Output = R> + 'static,
387 R: 'static,
388 {
389 let SystemRunner {
390 config,
391 runner,
392 signals,
393 fatal_signals,
394 panics,
395 ..
396 } = self;
397
398 if fatal_signals {
399 signals::enable_fatal_signals();
400 }
401 if panics {
402 signals::enable_panic_handling();
403 }
404
405 crate::driver::block_on_panic(runner.as_ref(), async move {
406 let (system, _) = System::start(config);
407 let _guard = SystemGuard(system.clone());
408 if signals {
409 system.enable_signals();
410 }
411
412 let loc = current_location();
413 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
414 fut.await
415 })
416 }
417
418 #[cfg(feature = "tokio")]
419 /// Runs the system's future in a local task set on an existing Tokio runtime.
420 ///
421 /// Does not invoke the configured [`Runner`] or apply the builder's signal
422 /// and panic-handling settings. The system and primary arbiter are installed
423 /// while the future runs, and shutdown callbacks are run on normal completion.
424 /// Like [`block_on`](Self::block_on), this waits for the future rather than
425 /// a stop request.
426 ///
427 /// # Panics
428 ///
429 /// Panics if used without a compatible Tokio runtime context. Panics from
430 /// the future propagate to the caller.
431 pub async fn run_local<F, R>(self, fut: F) -> R
432 where
433 F: Future<Output = R> + 'static,
434 R: 'static,
435 {
436 let SystemRunner { config, .. } = self;
437
438 // run loop
439 let result = tok_io::task::LocalSet::new()
440 .run_until(async move {
441 let (system, _) = System::start(config);
442 let _guard = SystemGuard(system);
443
444 let loc = current_location();
445 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
446 fut.await
447 })
448 .await;
449
450 crate::arbiter::run_shutdown_callbacks();
451 // SAFETY: The local task set and shutdown callbacks have ended, releasing storage borrows.
452 unsafe {
453 crate::remove_all_items();
454 }
455 if let Some(arb) = crate::Arbiter::try_current() {
456 arb.finalize();
457 }
458 result
459 }
460}
461
462/// Stops the system's arbiters and releases the signals for other systems when
463/// the system's future completes, fails, panics or is dropped.
464struct SystemGuard(System);
465
466impl Drop for SystemGuard {
467 fn drop(&mut self) {
468 self.0.disable_signals();
469
470 if std::thread::panicking() {
471 // some executors (compio) abort the process if a task is woken
472 // while the thread unwinds, stop arbiters from another thread
473 let list = self.0.take_arbiters();
474 if !list.is_empty() {
475 let stopped = std::thread::Builder::new()
476 .spawn(move || list.into_iter().for_each(|arb| arb.stop()))
477 .map(std::thread::JoinHandle::join);
478 if let Ok(Err(payload)) = stopped {
479 std::mem::forget(payload);
480 crate::abort_on_second_panic();
481 }
482 }
483 } else {
484 self.0.stop_arbiters();
485 }
486 }
487}
488
489#[track_caller]
490pub(crate) fn current_location() -> &'static panic::Location<'static> {
491 panic::Location::caller()
492}
493
494impl fmt::Debug for SystemRunner {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 f.debug_struct("SystemRunner")
497 .field("config", &self.config)
498 .finish()
499 }
500}
501
502#[cfg(test)]
503#[path = "../tests/unit/builder.rs"]
504mod tests;