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 signals: bool,
21 panics: bool,
23 pool_limit: usize,
25 pool_recv_timeout: time::Duration,
26 testing: bool,
28}
29
30impl Builder {
31 pub(super) fn new() -> Self {
32 Builder {
33 name: "ntex".into(),
34 stack_size: 0,
35 ping_interval: 2000,
36 ping_threshold: 1000,
37 signals: false,
38 panics: false,
39 testing: false,
40 pool_limit: 256,
41 pool_recv_timeout: time::Duration::from_mins(1),
42 }
43 }
44
45 #[must_use]
46 pub fn name<N: AsRef<str>>(mut self, name: N) -> Self {
48 self.name = name.as_ref().into();
49 self
50 }
51
52 #[doc(hidden)]
53 #[deprecated(since = "3.17.0")]
54 #[must_use]
55 pub fn stop_on_panic(self, _: bool) -> Self {
62 self
63 }
64
65 #[must_use]
66 pub fn signals(mut self, eanbled: bool) -> Self {
70 self.signals = eanbled;
71 self
72 }
73
74 #[must_use]
75 pub fn panic_handling(mut self, eanbled: bool) -> Self {
82 self.panics = eanbled;
83 self
84 }
85
86 #[doc(hidden)]
87 #[must_use]
88 pub fn disable_signals(mut self) -> Self {
92 self.signals = false;
93 self
94 }
95
96 #[doc(hidden)]
97 #[must_use]
98 pub fn enable_signals(mut self) -> Self {
102 self.signals = true;
103 self
104 }
105
106 #[must_use]
107 pub fn stack_size(mut self, size: usize) -> Self {
109 self.stack_size = size;
110 self
111 }
112
113 #[must_use]
114 pub fn ping_interval(mut self, interval: usize) -> Self {
119 self.ping_interval = interval;
120 self
121 }
122
123 #[must_use]
124 pub fn ping_threshold(mut self, interval: usize) -> Self {
131 self.ping_threshold = interval;
132 self
133 }
134
135 #[must_use]
136 pub fn thread_pool_limit(mut self, value: usize) -> Self {
140 self.pool_limit = value;
141 self
142 }
143
144 #[must_use]
145 pub fn testing(mut self) -> Self {
149 self.testing = true;
150 self.signals = false;
151 self.panics = false;
152 self
153 }
154
155 #[must_use]
156 pub fn thread_pool_recv_timeout<T>(mut self, timeout: T) -> Self
160 where
161 time::Duration: From<T>,
162 {
163 self.pool_recv_timeout = timeout.into();
164 self
165 }
166
167 pub fn build<R: Runner>(self, runner: R) -> SystemRunner {
173 let config = SystemConfig {
174 name: self.name.clone(),
175 testing: self.testing,
176 stack_size: self.stack_size,
177 ping_interval: self.ping_interval,
178 ping_threshold: self.ping_threshold,
179 pool_limit: self.pool_limit,
180 pool_recv_timeout: self.pool_recv_timeout,
181 runner: Arc::new(runner),
182 };
183 self.build_with(config)
184 }
185
186 pub fn build_with(self, config: SystemConfig) -> SystemRunner {
192 let runner = config.runner.clone();
193
194 SystemRunner {
196 config,
197 runner,
198 signals: self.signals,
199 panics: self.panics,
200 _t: PhantomData,
201 }
202 }
203}
204
205#[must_use = "SystemRunner must be run"]
207pub struct SystemRunner {
208 config: SystemConfig,
209 runner: Arc<dyn Runner>,
210 signals: bool,
211 panics: bool,
212 _t: PhantomData<Rc<()>>,
213}
214
215impl SystemRunner {
216 pub fn run_until_stop(self) -> io::Result<()> {
218 self.run(|| Ok(()))
219 }
220
221 pub fn run<F>(self, f: F) -> io::Result<()>
223 where
224 F: FnOnce() -> io::Result<()> + 'static,
225 {
226 log::info!("Starting {:?} system", self.config.name);
227
228 let SystemRunner {
229 config,
230 runner,
231 signals,
232 panics,
233 ..
234 } = self;
235
236 if panics {
237 signals::enable_panic_handling();
238 }
239
240 crate::driver::block_on_panic(runner.as_ref(), async move {
242 let (system, stop) = System::start(config);
243 let _signals = SignalsGuard(system.clone());
244 if signals {
245 system.enable_signals();
246 }
247
248 f()?;
249
250 let result = stop.await;
251
252 match result {
253 Ok(code) => {
254 if code != 0 {
255 Err(io::Error::other(format!("Non-zero exit code: {code}")))
256 } else {
257 Ok(())
258 }
259 }
260 Err(_) => Err(io::Error::other("Closed")),
261 }
262 })
263 }
264
265 #[allow(clippy::missing_panics_doc)]
266 pub fn block_on<F, R>(self, fut: F) -> R
268 where
269 F: Future<Output = R> + 'static,
270 R: 'static,
271 {
272 let SystemRunner {
273 config,
274 runner,
275 signals,
276 panics,
277 ..
278 } = self;
279
280 if panics {
281 signals::enable_panic_handling();
282 }
283
284 crate::driver::block_on_panic(runner.as_ref(), async move {
285 let (system, _) = System::start(config);
286 let _signals = SignalsGuard(system.clone());
287 if signals {
288 system.enable_signals();
289 }
290
291 let loc = current_location();
292 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
293 fut.await
294 })
295 }
296
297 #[cfg(feature = "tokio")]
298 pub async fn run_local<F, R>(self, fut: F) -> R
300 where
301 F: Future<Output = R> + 'static,
302 R: 'static,
303 {
304 let SystemRunner { config, .. } = self;
305
306 let result = tok_io::task::LocalSet::new()
308 .run_until(async move {
309 let (system, _) = System::start(config);
310 let _signals = SignalsGuard(system);
311
312 let loc = current_location();
313 ntex_error::set_backtrace_start(loc.file(), loc.line() + 2);
314 fut.await
315 })
316 .await;
317
318 crate::arbiter::run_shutdown_callbacks();
319 unsafe {
320 crate::remove_all_items();
321 }
322 result
323 }
324}
325
326struct SignalsGuard(System);
329
330impl Drop for SignalsGuard {
331 fn drop(&mut self) {
332 self.0.disable_signals();
333 }
334}
335
336#[track_caller]
337pub(crate) fn current_location() -> &'static panic::Location<'static> {
338 panic::Location::caller()
339}
340
341impl fmt::Debug for SystemRunner {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 f.debug_struct("SystemRunner")
344 .field("config", &self.config)
345 .finish()
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use std::{cell::Cell, cell::RefCell, time::Duration};
352
353 use super::*;
354 use crate::{Arbiter, testing::TestRunner};
355
356 #[test]
357 #[allow(deprecated)]
358 fn builder_options() {
359 let builder = System::build()
360 .name("opts")
361 .stop_on_panic(true)
362 .signals(false)
363 .panic_handling(false)
364 .enable_signals()
365 .disable_signals()
366 .stack_size(2 * 1024 * 1024)
367 .ping_interval(0)
368 .ping_threshold(10)
369 .thread_pool_limit(1)
370 .thread_pool_recv_timeout(Duration::from_millis(100))
371 .testing();
372 assert!(format!("{builder:?}").contains("opts"));
373
374 let runner = builder.build(TestRunner);
375 assert!(format!("{runner:?}").contains("opts"));
376 runner.block_on(async {
377 let sys = System::current();
378 assert_eq!(sys.name(), "opts");
379 assert!(sys.testing());
380 assert!(!sys.signals());
381
382 let mut arb = Arbiter::new();
384 arb.stop();
385 arb.join().unwrap();
386
387 assert_eq!(sys.spawn_blocking(|| 1).await, Ok(1));
388 });
389 }
390
391 #[test]
392 fn run_exit_codes() {
393 let err = System::new("test", TestRunner)
395 .run(|| Err(io::Error::other("init failed")))
396 .unwrap_err();
397 assert_eq!(err.to_string(), "init failed");
398
399 System::new("test", TestRunner)
400 .run(|| {
401 System::current().stop();
402 Ok(())
403 })
404 .unwrap();
405
406 let called = Rc::new(Cell::new(false));
408 let arb = Rc::new(RefCell::new(None));
409 let (called2, arb2) = (called.clone(), arb.clone());
410 let err = System::new("test", TestRunner)
411 .run(move || {
412 Arbiter::on_shutdown(move || called2.set(true));
413 *arb2.borrow_mut() = Some(Arbiter::new());
414 System::current().stop_with_code(3);
415 Ok(())
416 })
417 .unwrap_err();
418 assert_eq!(err.to_string(), "Non-zero exit code: 3");
419 assert!(called.get());
420
421 let mut arb = arb.borrow_mut().take().unwrap();
422 arb.join().unwrap();
423 assert!(!arb.is_running());
424 }
425}