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