Skip to main content

ntex_rt/
system.rs

1use std::any::{Any, TypeId};
2use std::collections::VecDeque;
3use std::sync::{Arc, atomic::AtomicBool, atomic::AtomicUsize, atomic::Ordering};
4use std::time::{Duration, Instant};
5use std::{cell::RefCell, fmt, future::Future, panic, pin::Pin, rc::Rc};
6
7use async_channel::{Receiver, Sender, unbounded};
8use futures_timer::Delay;
9use parking_lot::{Mutex, RwLock};
10
11use crate::arbiter::Arbiter;
12use crate::pool::ThreadPool;
13use crate::{BlockingResult, Builder, Handle, HashMap, HashSet, Runner, SystemRunner};
14
15static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
16
17thread_local!(
18    static PINGS: RefCell<HashMap<Id, VecDeque<PingRecord>>> =
19        RefCell::new(HashMap::default());
20);
21
22#[derive(Default)]
23struct Arbiters {
24    all: HashMap<Id, Arbiter>,
25    list: Vec<Arbiter>,
26}
27
28/// System id
29#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
30pub struct Id(pub(crate) usize);
31
32/// System is a runtime manager
33pub struct System(Arc<SystemInner>);
34
35struct SystemInner {
36    id: usize,
37    arbiter: Arbiter,
38    config: SystemConfig,
39    sender: Sender<SystemCommand>,
40    receiver: Receiver<SystemCommand>,
41    storage: RwLock<HashMap<TypeId, Box<dyn Any + Sync + Send>>>,
42    arbiters: Mutex<Arbiters>,
43    signals: AtomicBool,
44    pool: ThreadPool,
45}
46
47#[derive(Clone)]
48pub struct SystemConfig {
49    pub(super) name: String,
50    pub(super) stack_size: usize,
51    pub(super) stop_on_panic: bool,
52    pub(super) ping_interval: usize,
53    #[allow(dead_code)]
54    pub(super) ping_threshold: usize,
55    pub(super) pool_limit: usize,
56    pub(super) pool_recv_timeout: Duration,
57    pub(super) testing: bool,
58    pub(super) runner: Arc<dyn Runner>,
59}
60
61thread_local!(
62    static CURRENT: RefCell<Option<System>> = const { RefCell::new(None) };
63);
64
65impl Clone for System {
66    fn clone(&self) -> Self {
67        Self(self.0.clone())
68    }
69}
70
71impl System {
72    /// Constructs new system and sets it as current
73    pub(super) fn start(config: SystemConfig) -> (Self, oneshot::Receiver<i32>) {
74        let id = SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst);
75        let (sender, receiver) = unbounded();
76
77        let pool =
78            ThreadPool::new(&config.name, config.pool_limit, config.pool_recv_timeout);
79        let (arbiter, controller) = Arbiter::new_system(id, config.name.clone());
80
81        let mut arbiters = Arbiters::default();
82        arbiters.all.insert(arbiter.id(), arbiter.clone());
83        arbiters.list.push(arbiter.clone());
84
85        let sys = System(Arc::new(SystemInner {
86            id,
87            config,
88            arbiter,
89            sender,
90            receiver,
91            pool,
92            arbiters: Mutex::new(arbiters),
93            storage: RwLock::new(HashMap::default()),
94            signals: AtomicBool::new(false),
95        }));
96        System::set_current(sys.clone());
97
98        let (stop_tx, stop) = oneshot::channel();
99
100        // system support tasks
101        crate::spawn(SystemSupport::new(&sys, stop_tx).run());
102        crate::spawn(controller.run(sys.clone()));
103
104        (sys, stop)
105    }
106
107    /// Build a new system with a customized runtime
108    ///
109    /// This allows to customize the runtime. See struct level docs on
110    /// `Builder` for more information.
111    pub fn build() -> Builder {
112        Builder::new()
113    }
114
115    #[allow(clippy::new_ret_no_self)]
116    /// Create new system
117    ///
118    /// This method panics if it can not create runtime
119    pub fn new<R: Runner>(name: &str, runner: R) -> SystemRunner {
120        Self::build().name(name).build(runner)
121    }
122
123    #[allow(clippy::new_ret_no_self)]
124    /// Create new system
125    ///
126    /// This method panics if it can not create runtime
127    pub fn with_config(name: &str, config: SystemConfig) -> SystemRunner {
128        Self::build().name(name).build_with(config)
129    }
130
131    /// Get current running system
132    ///
133    /// # Panics
134    ///
135    /// Panics if System is not running
136    pub fn current() -> System {
137        CURRENT.with(|cell| match *cell.borrow() {
138            Some(ref sys) => sys.clone(),
139            None => panic!("System is not running"),
140        })
141    }
142
143    /// Runs a function using the system context.
144    pub fn try_current() -> Option<System> {
145        CURRENT.with(|cell| cell.borrow().as_ref().map(Clone::clone))
146    }
147
148    /// Set current running system
149    #[doc(hidden)]
150    pub fn set_current(sys: System) {
151        CURRENT.with(|s| {
152            *s.borrow_mut() = Some(sys);
153        });
154    }
155
156    pub(crate) fn register_arbiter(&self, arb: Arbiter) {
157        CURRENT.with(|s| {
158            *s.borrow_mut() = Some(self.clone());
159        });
160        let mut arbiters = self.0.arbiters.lock();
161        arbiters.all.insert(arb.id(), arb.clone());
162        arbiters.list.push(arb);
163    }
164
165    pub(crate) fn unregister_arbiter(&self, id: Id) {
166        CURRENT.with(|s| {
167            *s.borrow_mut() = None;
168        });
169        let mut arbiters = self.0.arbiters.lock();
170        if let Some(hnd) = arbiters.all.remove(&id) {
171            for (idx, arb) in arbiters.list.iter().enumerate() {
172                if &hnd == arb {
173                    arbiters.list.remove(idx);
174                    break;
175                }
176            }
177        }
178    }
179
180    pub(super) fn remove_current() {
181        CURRENT.with(|cell| {
182            cell.borrow_mut().take();
183        });
184    }
185
186    /// System id
187    pub fn id(&self) -> Id {
188        Id(self.0.id)
189    }
190
191    /// System name
192    pub fn name(&self) -> &str {
193        &self.0.config.name
194    }
195
196    /// Stop the system
197    pub fn stop(&self) {
198        self.stop_with_code(0);
199    }
200
201    /// Stop the system with a particular exit code
202    pub fn stop_with_code(&self, code: i32) {
203        let _ = self.0.sender.try_send(SystemCommand::Exit(code));
204    }
205
206    /// Return status of `stop_on_panic` option
207    ///
208    /// It controls whether the System is stopped when an
209    /// uncaught panic is thrown from a worker thread.
210    pub fn stop_on_panic(&self) -> bool {
211        self.0.config.stop_on_panic
212    }
213
214    /// Return status of `signals` option
215    pub fn signals(&self) -> bool {
216        self.0.signals.load(Ordering::Relaxed)
217    }
218
219    /// Enable `signals` handling
220    pub fn enable_signals(&self) {
221        if !self.signals() {
222            crate::signals::start(self);
223            self.0.signals.store(true, Ordering::Relaxed);
224        }
225    }
226
227    /// Disable `signals` handling
228    pub fn disable_signals(&self) {
229        if self.signals() {
230            crate::signals::stop(self);
231            self.0.signals.store(false, Ordering::Relaxed);
232        }
233    }
234
235    /// System arbiter
236    ///
237    /// # Panics
238    ///
239    /// Panics if system is not started
240    pub fn arbiter(&self) -> Arbiter {
241        self.0.arbiter.clone()
242    }
243
244    /// Retrieves a list of all arbiters in the system
245    ///
246    /// This method should be called from the thread where the system has been initialized,
247    /// typically the "main" thread.
248    pub fn list_arbiters<F, R>(&self, f: F) -> R
249    where
250        F: FnOnce(&[Arbiter]) -> R,
251    {
252        f(&self.0.arbiters.lock().list)
253    }
254
255    /// Retrieves a list of last pings records for each worker.
256    ///
257    /// This method should be called from the thread where the system has been initialized,
258    /// typically the "main" thread.
259    pub fn list_arbiter_pings<F>(mut f: F)
260    where
261        F: FnMut(&Arbiter, &mut VecDeque<PingRecord>),
262    {
263        PINGS.with(|pings| {
264            let mut p = pings.borrow_mut();
265            let sys = System::current();
266            let arbiters = sys.0.arbiters.lock();
267
268            for (id, recs) in &mut *p {
269                if let Some(arb) = arbiters.all.get(id) {
270                    f(arb, recs);
271                }
272            }
273        });
274    }
275
276    #[cfg(target_os = "linux")]
277    #[doc(hidden)]
278    /// Set arbiter latency callback.
279    ///
280    /// This callback is called when the arbiter response latency exceeds the
281    /// configured threshold. The provided backtrace is not resolved.
282    ///
283    /// Note: This callback is not thread-safe.
284    pub fn set_latency_callback<F: Fn(ntex_error::Backtrace) + 'static>(f: F) {
285        unsafe {
286            ARB_CB = Some(Box::new(f));
287        }
288    }
289
290    /// System config
291    pub fn config(&self) -> SystemConfig {
292        self.0.config.clone()
293    }
294
295    #[inline]
296    /// Runtime handle for main thread
297    pub fn handle(&self) -> Handle {
298        self.arbiter().handle().clone()
299    }
300
301    /// Testing flag
302    pub fn testing(&self) -> bool {
303        self.0.config.testing()
304    }
305
306    /// Spawns a blocking task on a new thread and waits for it to complete.
307    ///
308    /// If the returned future is dropped, the blocking task is cancelled.
309    /// Call `detach` to allow the task to continue running in the background.
310    pub fn spawn_blocking<F, R>(&self, f: F) -> BlockingResult<R>
311    where
312        F: FnOnce() -> R + Send + 'static,
313        R: Send + 'static,
314    {
315        self.0.pool.execute(f)
316    }
317
318    /// Returns a previously registered type, or inserts and returns a new one.
319    ///
320    /// This method acquires a lock on the internal data structure.
321    /// To avoid repeated locking, prefer storing a cloned value in the arbiter's storage.
322    pub fn get_value<T>(&self, f: impl FnOnce() -> T) -> T
323    where
324        T: Clone + Send + Sync + 'static,
325    {
326        if let Some(boxed) = self.0.storage.read().get(&TypeId::of::<T>())
327            && let Some(val) = (&**boxed as &(dyn Any + 'static)).downcast_ref::<T>()
328        {
329            val.clone()
330        } else {
331            let val = f();
332            self.0
333                .storage
334                .write()
335                .insert(TypeId::of::<T>(), Box::new(val.clone()));
336            val
337        }
338    }
339}
340
341impl SystemConfig {
342    #[inline]
343    /// Is current system is testing
344    pub fn testing(&self) -> bool {
345        self.testing
346    }
347}
348
349impl fmt::Debug for System {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        f.debug_struct("System")
352            .field("id", &self.0.id)
353            .field("config", &self.0.config)
354            .field("signals", &self.signals())
355            .field("pool", &self.0.pool)
356            .finish()
357    }
358}
359
360impl fmt::Debug for SystemConfig {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        f.debug_struct("SystemConfig")
363            .field("name", &self.name)
364            .field("testing", &self.testing)
365            .field("stack_size", &self.stack_size)
366            .field("stop_on_panic", &self.stop_on_panic)
367            .finish()
368    }
369}
370
371#[derive(Debug)]
372pub(super) enum SystemCommand {
373    Exit(i32),
374}
375
376#[derive(Debug)]
377struct SystemSupport {
378    sys: System,
379    stop: Option<oneshot::Sender<i32>>,
380    commands: Receiver<SystemCommand>,
381}
382
383impl SystemSupport {
384    fn new(sys: &System, stop: oneshot::Sender<i32>) -> Self {
385        Self {
386            sys: sys.clone(),
387            stop: Some(stop),
388            commands: sys.0.receiver.clone(),
389        }
390    }
391
392    async fn run(mut self) {
393        if self.sys.0.config.ping_interval != 0 {
394            crate::spawn(ping_arbiters(self.sys.clone()));
395        }
396
397        loop {
398            match self.commands.recv().await {
399                Ok(SystemCommand::Exit(code)) => {
400                    log::debug!("Stopping system with {code} code");
401
402                    // stop arbiters
403                    let mut arbiters = self.sys.0.arbiters.lock();
404                    for arb in arbiters.list.drain(..) {
405                        arb.stop();
406                    }
407                    arbiters.all.clear();
408
409                    // stop event loop
410                    if let Some(stop) = self.stop.take() {
411                        let _ = stop.send(code);
412                    }
413                }
414                Err(_) => {
415                    log::debug!("System stopped");
416                    return;
417                }
418            }
419        }
420    }
421}
422
423#[derive(Copy, Clone, Debug)]
424pub struct PingRecord {
425    /// Ping start time
426    pub start: Instant,
427    /// Round-trip time, if value is not set then ping is in process
428    pub rtt: Option<Duration>,
429}
430
431async fn ping_arbiters(sys: System) {
432    let pings = Rc::new(RefCell::new(HashSet::default()));
433    let interval = Duration::from_millis(sys.0.config.ping_interval as u64);
434    #[cfg(target_os = "linux")]
435    let threshold = Duration::from_millis(sys.0.config.ping_threshold as u64);
436
437    loop {
438        // interval between pings
439        Delay::new(interval).await;
440
441        // send pings
442        {
443            pings.borrow_mut().clear();
444
445            let start = Instant::now();
446            let arbiters = sys.0.arbiters.lock();
447
448            for arb in &arbiters.list {
449                let id = arb.id();
450                let pings = pings.clone();
451                let fut = arb.handle().spawn(async move {
452                    yield_to().await;
453                });
454
455                // calc ttl
456                PINGS.with(|pings| {
457                    let mut p = pings.borrow_mut();
458                    let recs = p.entry(arb.id()).or_default();
459                    recs.push_front(PingRecord { start, rtt: None });
460                    recs.truncate(10);
461                });
462
463                crate::spawn(async move {
464                    if fut.await.is_ok() {
465                        pings.borrow_mut().insert(id);
466
467                        PINGS.with(|pings| {
468                            pings
469                                .borrow_mut()
470                                .get_mut(&id)
471                                .unwrap()
472                                .front_mut()
473                                .unwrap()
474                                .rtt = Some(start.elapsed());
475                        });
476                    }
477                });
478            }
479        }
480
481        // check pings
482        #[cfg(target_os = "linux")]
483        {
484            const SPIN: Duration = Duration::from_micros(100);
485
486            // threshold
487            Delay::new(threshold).await;
488
489            let mut no_pongs = Vec::new();
490            {
491                for arb in &sys.0.arbiters.lock().list {
492                    let pong = pings.borrow_mut().remove(&arb.id());
493                    if !pong {
494                        no_pongs.push(arb.clone());
495                    }
496                }
497            }
498
499            if !crate::signals::is_enabled() {
500                continue;
501            }
502
503            for arb in no_pongs {
504                // no response from arbiter
505                log::error!("Arbiter {}({:?}) did not return pong", arb.name(), arb.id());
506
507                // send tgkill to thread id to capture backtrace
508                *CAPTURED.lock() = None;
509                EXPECTED_TID.store(arb.tid(), Ordering::Release);
510                let result = unsafe {
511                    libc::syscall(
512                        libc::SYS_tgkill,
513                        libc::getpid(),
514                        arb.tid(),
515                        libc::SIGUSR2,
516                    )
517                };
518
519                if result == -1 {
520                    log::error!(
521                        "Unsable to send SIGUSR2 to arbiter {}({:?}): {}",
522                        arb.name(),
523                        arb.id(),
524                        std::io::Error::last_os_error()
525                    );
526                } else {
527                    // Spin
528                    for _ in 0..1000 {
529                        Delay::new(SPIN).await;
530                        if let Some(bt) = CAPTURED.lock().take() {
531                            let bt = ntex_error::Backtrace::from(bt);
532                            #[allow(static_mut_refs)]
533                            if let Some(f) = unsafe { ARB_CB.as_ref() } {
534                                f(bt);
535                            } else {
536                                bt.resolver().resolve();
537                                log::error!(
538                                    "Worker does not returned pong within {interval:?} time.\n{bt:?}"
539                                );
540                            }
541                            break;
542                        }
543                    }
544                }
545            }
546        }
547    }
548}
549
550async fn yield_to() {
551    use std::task::{Context, Poll};
552
553    struct Yield {
554        completed: bool,
555    }
556
557    impl Future for Yield {
558        type Output = ();
559
560        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
561            if self.completed {
562                return Poll::Ready(());
563            }
564            self.completed = true;
565            cx.waker().wake_by_ref();
566            Poll::Pending
567        }
568    }
569
570    Yield { completed: false }.await;
571}
572
573#[cfg(target_os = "linux")]
574static mut ARB_CB: Option<Box<dyn Fn(ntex_error::Backtrace)>> = None;
575
576#[cfg(target_os = "linux")]
577static EXPECTED_TID: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
578#[cfg(target_os = "linux")]
579static CAPTURED: Mutex<Option<ntex_error::BacktraceRaw>> = Mutex::new(None);
580
581#[track_caller]
582#[cfg(target_family = "unix")]
583pub(crate) fn sig_usr2() {
584    #[cfg(target_os = "linux")]
585    #[allow(clippy::cast_possible_truncation)]
586    {
587        let tid = unsafe { libc::syscall(libc::SYS_gettid) } as i32;
588        if EXPECTED_TID.load(Ordering::Acquire) == tid {
589            // backtrace::Backtrace::new_unresolved uses libunwind frame walking,
590            // which is signal-safe. Symbol resolution is NOT — do it later.
591            let bt = ntex_error::BacktraceRaw::new(panic::Location::caller());
592            *CAPTURED.lock() = Some(bt);
593        }
594    }
595}