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