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