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