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