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)]
6pub struct Builder {
11 name: String,
13 stack_size: usize,
15 ping_interval: usize,
17 ping_threshold: usize,
19 latency_interval: usize,
21 signals: bool,
23 panics: bool,
25 pool_limit: usize,
27 pool_recv_timeout: time::Duration,
28 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 pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
51 self.name = name.as_ref().into();
52 self
53 }
54
55 #[must_use]
56 pub fn signals(mut self, eanbled: bool) -> Self {
60 self.signals = eanbled;
61 self
62 }
63
64 #[must_use]
65 pub fn panic_handling(mut self, eanbled: bool) -> Self {
72 self.panics = eanbled;
73 self
74 }
75
76 #[doc(hidden)]
77 #[must_use]
78 pub fn disable_signals(mut self) -> Self {
82 self.signals = false;
83 self
84 }
85
86 #[doc(hidden)]
87 #[must_use]
88 pub fn enable_signals(mut self) -> Self {
92 self.signals = true;
93 self
94 }
95
96 #[must_use]
97 pub fn stack_size(mut self, size: usize) -> Self {
99 self.stack_size = size;
100 self
101 }
102
103 #[must_use]
104 pub fn ping_interval(mut self, interval: usize) -> Self {
109 self.ping_interval = interval;
110 self
111 }
112
113 #[must_use]
114 pub fn ping_threshold(mut self, interval: usize) -> Self {
124 self.ping_threshold = interval;
125 self
126 }
127
128 #[must_use]
129 pub fn latency_interval(mut self, interval: usize) -> Self {
137 self.latency_interval = interval;
138 self
139 }
140
141 #[must_use]
142 pub fn thread_pool_limit(mut self, value: usize) -> Self {
146 self.pool_limit = value;
147 self
148 }
149
150 #[must_use]
151 pub fn testing(mut self) -> Self {
155 self.testing = true;
156 self.signals = false;
157 self.panics = false;
158 self
159 }
160
161 #[must_use]
162 pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
166 where
167 time::Duration: From<T>,
168 {
169 self.pool_recv_timeout = timeout.into();
170 self
171 }
172
173 pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
179 let config = SystemConfig {
180 name: self.name.clone(),
181 testing: self.testing,
182 stack_size: self.stack_size,
183 ping_interval: self.ping_interval,
184 ping_threshold: self.ping_threshold,
185 latency_interval: self.latency_interval,
186 pool_limit: self.pool_limit,
187 pool_recv_timeout: self.pool_recv_timeout,
188 runner: Arc::new(runner),
189 };
190 self.build_with(config)
191 }
192
193 pub fn build_with(self, config: SystemConfig) -> SystemRunner {
199 let runner = config.runner.clone();
200
201 SystemRunner {
203 config,
204 runner,
205 signals: self.signals,
206 panics: self.panics,
207 _t: PhantomData,
208 }
209 }
210}
211
212#[must_use = "SystemRunner must be run"]
214pub struct SystemRunner {
215 config: SystemConfig,
216 runner: Arc<dyn Runner>,
217 signals: bool,
218 panics: bool,
219 _t: PhantomData<Rc<()>>,
220}
221
222impl SystemRunner {
223 pub fn run_until_stop(self) -> io::Result<()> {
225 self.run(|| Ok(()))
226 }
227
228 pub fn run<F>(self, f: F) -> io::Result<()>
230 where
231 F: FnOnce() -> io::Result<()> + 'static,
232 {
233 log::info!("Starting {:?} system", self.config.name);
234
235 let SystemRunner {
236 config,
237 runner,
238 signals,
239 panics,
240 ..
241 } = self;
242
243 if panics {
244 signals::enable_panic_handling();
245 }
246
247 crate::driver::block_on_panic(runner.as_ref(), async move {
249 let (system, stop) = System::start(config);
250 let _guard = SystemGuard(system.clone());
251 if signals {
252 system.enable_signals();
253 }
254
255 f()?;
256
257 let result = stop.await;
258
259 match result {
260 Ok(code) => {
261 if code != 0 {
262 Err(io::Error::other(format!("Non-zero exit code: {code}")))
263 } else {
264 Ok(())
265 }
266 }
267 Err(_) => Err(io::Error::other("Closed")),
268 }
269 })
270 }
271
272 #[allow(clippy::missing_panics_doc)]
273 pub fn block_on<F, R>(self, fut: F) -> R
275 where
276 F: Future<Output = R> + 'static,
277 R: 'static,
278 {
279 let SystemRunner {
280 config,
281 runner,
282 signals,
283 panics,
284 ..
285 } = self;
286
287 if panics {
288 signals::enable_panic_handling();
289 }
290
291 crate::driver::block_on_panic(runner.as_ref(), async move {
292 let (system, _) = System::start(config);
293 let _guard = SystemGuard(system.clone());
294 if signals {
295 system.enable_signals();
296 }
297
298 let loc = current_location();
299 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
300 fut.await
301 })
302 }
303
304 #[cfg(feature = "tokio")]
305 pub async fn run_local<F, R>(self, fut: F) -> R
307 where
308 F: Future<Output = R> + 'static,
309 R: 'static,
310 {
311 let SystemRunner { config, .. } = self;
312
313 let result = tok_io::task::LocalSet::new()
315 .run_until(async move {
316 let (system, _) = System::start(config);
317 let _guard = SystemGuard(system);
318
319 let loc = current_location();
320 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
321 fut.await
322 })
323 .await;
324
325 crate::arbiter::run_shutdown_callbacks();
326 unsafe {
327 crate::remove_all_items();
328 }
329 if let Some(arb) = crate::Arbiter::try_current() {
330 arb.finalize();
331 }
332 result
333 }
334}
335
336struct SystemGuard(System);
339
340impl Drop for SystemGuard {
341 fn drop(&mut self) {
342 self.0.disable_signals();
343
344 if std::thread::panicking() {
345 let list = self.0.take_arbiters();
348 if !list.is_empty() {
349 let _ = std::thread::Builder::new()
350 .spawn(move || list.into_iter().for_each(|arb| arb.stop()))
351 .map(std::thread::JoinHandle::join);
352 }
353 } else {
354 self.0.stop_arbiters();
355 }
356 }
357}
358
359#[track_caller]
360pub(crate) fn current_location() -> &'static panic::Location<'static> {
361 panic::Location::caller()
362}
363
364impl fmt::Debug for SystemRunner {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 f.debug_struct("SystemRunner")
367 .field("config", &self.config)
368 .finish()
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use std::sync::atomic::{AtomicUsize, Ordering};
375 use std::{cell::Cell, cell::RefCell, time::Duration};
376
377 use super::*;
378 use crate::{Arbiter, testing::TestRunner};
379
380 #[test]
381 fn builder_options() {
382 let builder = System::build()
383 .name("opts")
384 .signals(false)
385 .panic_handling(false)
386 .enable_signals()
387 .disable_signals()
388 .stack_size(2 * 1024 * 1024)
389 .ping_interval(0)
390 .ping_threshold(10)
391 .thread_pool_limit(1)
392 .thread_pool_recv_timeout(Duration::from_millis(100))
393 .testing();
394 assert!(format!("{builder:?}").contains("opts"));
395
396 let runner = builder.build(TestRunner);
397 assert!(format!("{runner:?}").contains("opts"));
398 runner.block_on(async {
399 let sys = System::current();
400 assert_eq!(sys.name(), "opts");
401 assert!(sys.testing());
402 assert!(!sys.signals());
403
404 let mut arb = Arbiter::new();
406 arb.stop();
407 arb.join().unwrap();
408
409 assert_eq!(sys.spawn_blocking(|| 1).await, Ok(1));
410 });
411 }
412
413 #[test]
414 fn run_exit_codes() {
415 let err = System::new("test", TestRunner)
417 .run(|| Err(io::Error::other("init failed")))
418 .unwrap_err();
419 assert_eq!(err.to_string(), "init failed");
420
421 System::new("test", TestRunner)
422 .run(|| {
423 System::current().stop();
424 Ok(())
425 })
426 .unwrap();
427
428 let called = Rc::new(Cell::new(false));
430 let arb = Rc::new(RefCell::new(None));
431 let (called2, arb2) = (called.clone(), arb.clone());
432 let err = System::new("test", TestRunner)
433 .run(move || {
434 Arbiter::on_shutdown(move || called2.set(true));
435 *arb2.borrow_mut() = Some(Arbiter::new());
436 System::current().stop_with_code(3);
437 Ok(())
438 })
439 .unwrap_err();
440 assert_eq!(err.to_string(), "Non-zero exit code: 3");
441 assert!(called.get());
442
443 let mut arb = arb.borrow_mut().take().unwrap();
444 arb.join().unwrap();
445 assert!(!arb.is_running());
446 }
447
448 #[test]
449 fn exit_stops_child_arbiters() {
450 fn assert_stopped(arb: &Rc<RefCell<Option<Arbiter>>>) {
451 let arb = arb.borrow_mut().take().unwrap();
452 let mut arb2 = arb.clone();
453 let (tx, rx) = std::sync::mpsc::channel();
454 std::thread::spawn(move || {
455 let _ = tx.send(arb2.join().is_ok());
456 });
457 let stopped = rx.recv_timeout(Duration::from_secs(5));
458 arb.stop();
459 assert_eq!(stopped, Ok(true), "child arbiter is still running");
460 }
461
462 let arb = Rc::new(RefCell::new(None));
464 let arb2 = arb.clone();
465 let err = System::new("test", TestRunner)
466 .run(move || {
467 *arb2.borrow_mut() = Some(Arbiter::new());
468 Err(io::Error::other("init failed"))
469 })
470 .unwrap_err();
471 assert_eq!(err.to_string(), "init failed");
472 assert_stopped(&arb);
473
474 let arb2 = arb.clone();
476 System::new("test", TestRunner).block_on(async move {
477 *arb2.borrow_mut() = Some(Arbiter::new());
478 });
479 assert_stopped(&arb);
480
481 let arb2 = arb.clone();
483 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
484 System::new("test", TestRunner).block_on(async move {
485 *arb2.borrow_mut() = Some(Arbiter::new());
486 panic!("root future panic");
487 });
488 }));
489 assert!(result.is_err());
490 assert_stopped(&arb);
491 }
492
493 #[test]
494 fn primary_arbiter_finalized() {
495 fn with_on_stop(calls: &Arc<AtomicUsize>) -> Arbiter {
496 let calls = calls.clone();
497 Arbiter::current().on_stop(move || {
498 calls.fetch_add(1, Ordering::Relaxed);
499 })
500 }
501
502 let calls = Arc::new(AtomicUsize::new(0));
504 let calls2 = calls.clone();
505 let arb = System::new("test", TestRunner).block_on(async move { with_on_stop(&calls2) });
506 assert!(!arb.is_running());
507 assert_eq!(calls.load(Ordering::Relaxed), 1);
508
509 let calls = Arc::new(AtomicUsize::new(0));
511 let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
512 let slot2 = slot.clone();
513 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
514 System::new("test", TestRunner).block_on(async move {
515 *slot2.borrow_mut() = Some(with_on_stop(&calls2));
516 panic!("root future panic");
517 });
518 }));
519 assert!(result.is_err());
520 assert!(!slot.borrow().as_ref().unwrap().is_running());
521 assert_eq!(calls.load(Ordering::Relaxed), 1);
522
523 let calls = Arc::new(AtomicUsize::new(0));
525 let (calls2, slot) = (calls.clone(), Rc::new(RefCell::new(None)));
526 let slot2 = slot.clone();
527 System::new("test", TestRunner)
528 .run(move || {
529 *slot2.borrow_mut() = Some(with_on_stop(&calls2));
530 System::current().stop();
531 Ok(())
532 })
533 .unwrap();
534 assert!(!slot.borrow().as_ref().unwrap().is_running());
535 assert_eq!(calls.load(Ordering::Relaxed), 1);
536 }
537
538 #[cfg(feature = "tokio")]
539 #[test]
540 fn run_local_finalizes_primary_arbiter() {
541 let calls = Arc::new(AtomicUsize::new(0));
542 let calls2 = calls.clone();
543 let rt = tok_io::runtime::Builder::new_current_thread()
544 .enable_all()
545 .build()
546 .unwrap();
547 let arb = rt.block_on(System::new("test", TestRunner).run_local(async move {
548 Arbiter::current().on_stop(move || {
549 calls2.fetch_add(1, Ordering::Relaxed);
550 })
551 }));
552 assert!(!arb.is_running());
553 assert_eq!(calls.load(Ordering::Relaxed), 1);
554 }
555}