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, pin::Pin};
6
7use async_channel::{Receiver, Sender, unbounded};
8use futures_timer::Delay;
9use parking_lot::{Mutex, RwLock};
10
11use crate::arbiter::Arbiter;
12use crate::pool::{PoolMetrics, ThreadPool};
13use crate::{BlockingResult, Builder, Handle, HashMap, 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(pub(crate) Arc<SystemInner>);
36
37pub(crate) struct SystemInner {
38    id: usize,
39    arbiter: Arbiter,
40    pub(crate) 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    pub(super) ping_threshold: usize,
56    pub(super) latency_interval: 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 SystemInner {
74    /// Returns the registered arbiters.
75    pub(crate) fn arbiters(&self) -> Vec<Arbiter> {
76        self.arbiters.lock().list.clone()
77    }
78}
79
80impl System {
81    /// Constructs new system and sets it as current
82    pub(super) fn start(config: SystemConfig) -> (Self, oneshot::Receiver<i32>) {
83        let id = SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst);
84        let (sender, receiver) = unbounded();
85
86        let pool = ThreadPool::new(&config.name, config.pool_limit, config.pool_recv_timeout);
87        let (arbiter, controller) = Arbiter::new_system(id, config.name.clone());
88
89        let mut arbiters = Arbiters::default();
90        arbiters.all.insert(arbiter.id(), arbiter.clone());
91        arbiters.list.push(arbiter.clone());
92
93        let sys = System(Arc::new(SystemInner {
94            id,
95            config,
96            arbiter,
97            sender,
98            receiver,
99            pool,
100            arbiters: Mutex::new(arbiters),
101            storage: RwLock::new(HashMap::default()),
102            signals: AtomicBool::new(false),
103        }));
104        System::set_current(sys.clone());
105
106        let (stop_tx, stop) = oneshot::channel();
107
108        // system support tasks
109        crate::spawn_detached(SystemSupport::new(&sys, stop_tx).run());
110        crate::spawn_detached(controller.run(sys.clone()));
111
112        (sys, stop)
113    }
114
115    /// Creates a builder for a system with a customized runtime.
116    ///
117    /// See [`Builder`] for the available configuration options.
118    pub fn build() -> Builder {
119        Builder::new()
120    }
121
122    #[allow(clippy::new_ret_no_self)]
123    /// Creates a system runner with the specified name and runtime runner.
124    ///
125    /// # Panics
126    ///
127    /// Panics if the runtime cannot be created.
128    pub fn new<R: Runner>(name: &str, runner: R) -> SystemRunner {
129        Self::build().name(name).build(runner)
130    }
131
132    #[allow(clippy::new_ret_no_self)]
133    /// Creates a system runner from an existing configuration.
134    ///
135    /// # Panics
136    ///
137    /// Panics if the runtime cannot be created.
138    pub fn with_config(name: &str, mut config: SystemConfig) -> SystemRunner {
139        name.clone_into(&mut config.name);
140        Self::build().name(name).build_with(config)
141    }
142
143    /// Returns the system running on the current thread.
144    ///
145    /// # Panics
146    ///
147    /// Panics if no system is running on the current thread.
148    pub fn current() -> System {
149        CURRENT.with(|cell| match *cell.borrow() {
150            Some(ref sys) => sys.clone(),
151            None => panic!("System is not running"),
152        })
153    }
154
155    /// Returns the system running on the current thread, if one exists.
156    pub fn try_current() -> Option<System> {
157        CURRENT.with(|cell| cell.borrow().as_ref().map(Clone::clone))
158    }
159
160    /// Set current running system
161    #[doc(hidden)]
162    pub fn set_current(sys: System) {
163        CURRENT.with(|s| {
164            *s.borrow_mut() = Some(sys);
165        });
166    }
167
168    pub(crate) fn register_arbiter(&self, arb: Arbiter) {
169        CURRENT.with(|s| {
170            *s.borrow_mut() = Some(self.clone());
171        });
172        let mut arbiters = self.0.arbiters.lock();
173        arbiters.all.insert(arb.id(), arb.clone());
174        arbiters.list.push(arb);
175    }
176
177    pub(crate) fn unregister_arbiter(&self, id: Id) {
178        CURRENT.with(|s| {
179            *s.borrow_mut() = None;
180        });
181        let mut arbiters = self.0.arbiters.lock();
182        if let Some(hnd) = arbiters.all.remove(&id) {
183            for (idx, arb) in arbiters.list.iter().enumerate() {
184                if &hnd == arb {
185                    arbiters.list.remove(idx);
186                    break;
187                }
188            }
189        }
190    }
191
192    pub(super) fn remove_current() {
193        CURRENT.with(|cell| {
194            cell.borrow_mut().take();
195        });
196    }
197
198    /// Unregisters all arbiters of the system.
199    pub(super) fn take_arbiters(&self) -> Vec<Arbiter> {
200        let mut arbiters = self.0.arbiters.lock();
201        arbiters.all.clear();
202        std::mem::take(&mut arbiters.list)
203    }
204
205    /// Stops and unregisters all arbiters of the system.
206    pub(super) fn stop_arbiters(&self) {
207        for arb in self.take_arbiters() {
208            arb.stop();
209        }
210    }
211
212    /// Returns the system identifier.
213    pub fn id(&self) -> Id {
214        Id(self.0.id)
215    }
216
217    /// Returns the system name.
218    pub fn name(&self) -> &str {
219        &self.0.config.name
220    }
221
222    /// Stops the system with exit code `0`.
223    pub fn stop(&self) {
224        self.stop_with_code(0);
225    }
226
227    /// Stops the system with the specified exit code.
228    pub fn stop_with_code(&self, code: i32) {
229        let _ = self.0.sender.try_send(SystemCommand::Exit(code));
230    }
231
232    /// Returns whether process signal handling is enabled.
233    pub fn signals(&self) -> bool {
234        self.0.signals.load(Ordering::Relaxed)
235    }
236
237    /// Enables process signal handling.
238    ///
239    /// Signals are handled by one system at a time, this has no effect while
240    /// another system handles signals.
241    pub fn enable_signals(&self) {
242        if !self.signals() && crate::signals::start(self) {
243            self.0.signals.store(true, Ordering::Relaxed);
244        }
245    }
246
247    /// Disables process signal handling.
248    pub fn disable_signals(&self) {
249        if self.signals() {
250            crate::signals::stop(self);
251            self.0.signals.store(false, Ordering::Relaxed);
252        }
253    }
254
255    /// Returns the system's primary arbiter.
256    ///
257    /// # Panics
258    ///
259    /// Panics if the system has not been started.
260    pub fn arbiter(&self) -> Arbiter {
261        self.0.arbiter.clone()
262    }
263
264    /// Provides access to all arbiters registered with this system.
265    ///
266    /// The callback receives a snapshot of the registered arbiters.
267    ///
268    /// This method should be called from the thread where the system has been initialized,
269    /// typically the "main" thread.
270    pub fn list_arbiters<F, R>(&self, f: F) -> R
271    where
272        F: FnOnce(&[Arbiter]) -> R,
273    {
274        f(&self.0.arbiters())
275    }
276
277    /// Visits the latest ping records for each registered arbiter.
278    ///
279    /// This method should be called from the thread where the system has been initialized,
280    /// typically the "main" thread.
281    pub fn list_arbiter_pings<F>(mut f: F)
282    where
283        F: FnMut(&Arbiter, &mut VecDeque<PingRecord>),
284    {
285        PINGS.with(|pings| {
286            let mut p = pings.borrow_mut();
287            let sys = System::current();
288            let arbiters: Vec<_> = {
289                let arbiters = sys.0.arbiters.lock();
290                p.keys()
291                    .filter_map(|id| arbiters.all.get(id).map(|arb| (*id, arb.clone())))
292                    .collect()
293            };
294
295            // callbacks run without the registry lock
296            for (id, arb) in &arbiters {
297                if let Some(recs) = p.get_mut(id) {
298                    f(arb, recs);
299                }
300            }
301        });
302    }
303
304    #[doc(hidden)]
305    /// Set arbiter latency callback.
306    ///
307    /// This callback is called with the backtrace of a stalled arbiter, see
308    /// [`Builder::ping_threshold`]. Backtraces are captured on Linux and
309    /// Windows. The provided backtrace is not resolved. The location is the
310    /// spawn location of the task that blocks the arbiter, see
311    /// [`Handle::current_task`].
312    pub fn set_latency_callback<F>(f: F)
313    where
314        F: Fn(ntex_error::Backtrace, Option<&'static std::panic::Location<'static>>)
315            + Send
316            + Sync
317            + 'static,
318    {
319        crate::watchdog::set_latency_callback(Arc::new(f));
320    }
321
322    /// Returns a clone of the system configuration.
323    pub fn config(&self) -> SystemConfig {
324        self.0.config.clone()
325    }
326
327    #[inline]
328    /// Returns a runtime handle for the primary arbiter.
329    pub fn handle(&self) -> Handle {
330        self.arbiter().handle().clone()
331    }
332
333    /// Returns whether the system is configured for testing.
334    pub fn testing(&self) -> bool {
335        self.0.config.testing()
336    }
337
338    /// Spawns a blocking task on a new thread and waits for it to complete.
339    ///
340    /// Dropping the returned future prevents queued work from starting, but
341    /// cannot interrupt work that is already running. Call
342    /// [`BlockingResult::detach`] to let queued work continue even if its
343    /// result is no longer needed.
344    pub fn spawn_blocking<F, R>(&self, f: F) -> BlockingResult<R>
345    where
346        F: FnOnce() -> R + Send + 'static,
347        R: Send + 'static,
348    {
349        self.0.pool.execute(f)
350    }
351
352    /// Returns a snapshot of the blocking thread pool metrics.
353    pub fn pool_metrics(&self) -> PoolMetrics {
354        self.0.pool.metrics()
355    }
356
357    /// Returns a previously registered type, or inserts and returns a new one.
358    ///
359    /// This method acquires a lock on the internal data structure.
360    /// To avoid repeated locking, prefer storing a cloned value in the arbiter's storage.
361    pub fn get_value<T>(&self, f: impl FnOnce() -> T) -> T
362    where
363        T: Clone + Send + Sync + 'static,
364    {
365        if let Some(boxed) = self.0.storage.read().get(&TypeId::of::<T>())
366            && let Some(val) = (&**boxed as &(dyn Any + 'static)).downcast_ref::<T>()
367        {
368            val.clone()
369        } else {
370            let val = f();
371            self.0
372                .storage
373                .write()
374                .insert(TypeId::of::<T>(), Box::new(val.clone()));
375            val
376        }
377    }
378}
379
380impl SystemConfig {
381    #[inline]
382    /// Returns whether the system is configured for testing.
383    pub fn testing(&self) -> bool {
384        self.testing
385    }
386}
387
388impl fmt::Debug for System {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        f.debug_struct("System")
391            .field("id", &self.0.id)
392            .field("config", &self.0.config)
393            .field("signals", &self.signals())
394            .field("pool", &self.0.pool)
395            .finish()
396    }
397}
398
399impl fmt::Debug for SystemConfig {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        f.debug_struct("SystemConfig")
402            .field("name", &self.name)
403            .field("testing", &self.testing)
404            .field("stack_size", &self.stack_size)
405            .finish()
406    }
407}
408
409#[derive(Debug)]
410pub(super) enum SystemCommand {
411    Exit(i32),
412}
413
414#[derive(Debug)]
415struct SystemSupport {
416    sys: System,
417    stop: Option<oneshot::Sender<i32>>,
418    commands: Receiver<SystemCommand>,
419    watchdog: Option<Arc<AtomicBool>>,
420}
421
422impl Drop for SystemSupport {
423    fn drop(&mut self) {
424        self.stop_watchdog();
425    }
426}
427
428impl SystemSupport {
429    fn new(sys: &System, stop: oneshot::Sender<i32>) -> Self {
430        Self {
431            sys: sys.clone(),
432            stop: Some(stop),
433            commands: sys.0.receiver.clone(),
434            watchdog: None,
435        }
436    }
437
438    fn stop_watchdog(&mut self) {
439        if let Some(stop) = self.watchdog.take() {
440            stop.store(true, Ordering::Relaxed);
441        }
442    }
443
444    async fn run(mut self) {
445        crate::metrics::start_latency_sampler(
446            &self.sys.0.arbiter.0.latency,
447            self.sys.0.config.latency_interval,
448        );
449        self.watchdog = crate::watchdog::start(&self.sys);
450        if self.sys.0.config.ping_interval != 0 {
451            crate::spawn_detached(ping_arbiters(self.sys.clone()));
452        }
453
454        loop {
455            match self.commands.recv().await {
456                Ok(SystemCommand::Exit(code)) => {
457                    log::debug!("Stopping system with {code} code");
458                    self.stop_watchdog();
459                    self.sys.stop_arbiters();
460
461                    crate::arbiter::run_shutdown_callbacks();
462
463                    // stop event loop
464                    if let Some(stop) = self.stop.take() {
465                        let _ = stop.send(code);
466                    }
467                }
468                Err(_) => {
469                    log::debug!("System stopped");
470                    return;
471                }
472            }
473        }
474    }
475}
476
477#[derive(Copy, Clone, Debug)]
478pub struct PingRecord {
479    /// Ping start time
480    pub start: Instant,
481    /// Round-trip time, if value is not set then ping is in process
482    pub rtt: Option<Duration>,
483}
484
485async fn ping_arbiters(sys: System) {
486    let interval = Duration::from_millis(sys.0.config.ping_interval as u64);
487
488    loop {
489        // interval between pings
490        Delay::new(interval).await;
491
492        let start = Instant::now();
493        let arbiters = sys.0.arbiters.lock();
494
495        // drop records of stopped arbiters
496        PINGS.with(|pings| {
497            pings
498                .borrow_mut()
499                .retain(|id, _| arbiters.all.contains_key(id));
500        });
501
502        for arb in &arbiters.list {
503            let id = arb.id();
504            let fut = arb.handle().spawn(async move {
505                yield_to().await;
506            });
507
508            PINGS.with(|pings| {
509                let mut p = pings.borrow_mut();
510                let recs = p.entry(id).or_default();
511                recs.push_front(PingRecord { start, rtt: None });
512                recs.truncate(10);
513            });
514
515            crate::spawn_detached(async move {
516                if fut.await.is_ok() {
517                    PINGS.with(|pings| {
518                        // a late pong belongs to its own round
519                        if let Some(recs) = pings.borrow_mut().get_mut(&id)
520                            && let Some(rec) = recs.iter_mut().find(|r| r.start == start)
521                        {
522                            rec.rtt = Some(start.elapsed());
523                        }
524                    });
525                }
526            });
527        }
528    }
529}
530
531async fn yield_to() {
532    use std::task::{Context, Poll};
533
534    struct Yield {
535        completed: bool,
536    }
537
538    impl Future for Yield {
539        type Output = ();
540
541        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
542            if self.completed {
543                return Poll::Ready(());
544            }
545            self.completed = true;
546            cx.waker().wake_by_ref();
547            Poll::Pending
548        }
549    }
550
551    Yield { completed: false }.await;
552}
553
554#[cfg(test)]
555mod ping_tests {
556    use super::*;
557    use crate::testing::TestRunner;
558
559    #[test]
560    fn ping_records_pruned() {
561        System::build()
562            .name("test")
563            .ping_interval(5)
564            .build(TestRunner)
565            .block_on(async {
566                let mut arb = Arbiter::new();
567                let id = arb.id();
568                let answered = || {
569                    PINGS.with(|p| {
570                        p.borrow()
571                            .get(&id)
572                            .is_some_and(|recs| recs.iter().any(|r| r.rtt.is_some()))
573                    })
574                };
575                let has = || PINGS.with(|p| p.borrow().contains_key(&id));
576
577                for _ in 0..400 {
578                    if answered() {
579                        break;
580                    }
581                    Delay::new(Duration::from_millis(5)).await;
582                }
583                assert!(answered());
584
585                arb.stop();
586                arb.join().unwrap();
587                for _ in 0..400 {
588                    if !has() {
589                        break;
590                    }
591                    Delay::new(Duration::from_millis(5)).await;
592                }
593                assert!(!has());
594            });
595    }
596
597    #[test]
598    fn latency_sampling() {
599        System::build()
600            .name("test")
601            .latency_interval(5)
602            .build(TestRunner)
603            .block_on(async {
604                let mut arb = Arbiter::new();
605                let sampled = |arb: &Arbiter| arb.latency().unwrap().histogram.count() >= 2;
606                let sys_arb = System::current().arbiter();
607
608                for _ in 0..400 {
609                    if sampled(&arb) && sampled(&sys_arb) {
610                        break;
611                    }
612                    Delay::new(Duration::from_millis(5)).await;
613                }
614                assert!(sampled(&arb));
615                assert!(sampled(&sys_arb));
616
617                let m = arb.latency().unwrap();
618                assert_eq!(m.interval, Duration::from_millis(5));
619                assert!(m.last_sample.unwrap() <= Instant::now());
620
621                arb.stop();
622                arb.join().unwrap();
623            });
624
625        System::build()
626            .name("test")
627            .latency_interval(0)
628            .build(TestRunner)
629            .block_on(async {
630                assert_eq!(System::current().arbiter().latency(), None);
631                let mut arb = Arbiter::new();
632                assert_eq!(arb.latency(), None);
633                arb.stop();
634                arb.join().unwrap();
635            });
636    }
637}
638
639#[cfg(test)]
640mod api_tests {
641    use std::sync::atomic::{AtomicUsize, Ordering};
642
643    use super::*;
644    use crate::testing::TestRunner;
645
646    #[test]
647    #[should_panic(expected = "System is not running")]
648    fn current_without_system() {
649        assert!(System::try_current().is_none());
650        let _ = System::current();
651    }
652
653    #[test]
654    fn system_api() {
655        let config = System::new("base", TestRunner).block_on(async {
656            let sys = System::current();
657            assert_eq!(sys.name(), "base");
658            assert!(!sys.testing());
659            assert!(!sys.signals());
660            assert_eq!(System::try_current().unwrap().id(), sys.id());
661            assert_eq!(sys.arbiter(), Arbiter::current());
662            assert!(format!("{sys:?}").contains("base"));
663            assert!(format!("{:?}", sys.config()).contains("base"));
664
665            // values are stored once per system
666            assert_eq!(sys.get_value(|| 1usize), 1);
667            assert_eq!(sys.get_value(|| 2usize), 1);
668
669            assert_eq!(sys.spawn_blocking(|| 10).await, Ok(10));
670
671            let cnt = Arc::new(AtomicUsize::new(0));
672            let cnt2 = cnt.clone();
673            sys.handle()
674                .spawn(async move {
675                    cnt2.fetch_add(1, Ordering::Relaxed);
676                })
677                .await
678                .unwrap();
679            assert_eq!(cnt.load(Ordering::Relaxed), 1);
680            sys.list_arbiters(|arbs| assert_eq!(arbs, [sys.arbiter()]));
681            sys.config()
682        });
683        assert!(System::try_current().is_none());
684
685        System::with_config("copy", config).block_on(async {
686            let sys = System::current();
687            assert_eq!(sys.name(), "copy");
688            assert_eq!(Arbiter::current().name(), "copy");
689        });
690    }
691
692    #[test]
693    fn list_arbiter_pings() {
694        System::build()
695            .name("test")
696            .ping_interval(5)
697            .build(TestRunner)
698            .block_on(async {
699                let mut arb = Arbiter::new();
700                let visited = || {
701                    let mut found = false;
702                    System::list_arbiter_pings(|a, recs| {
703                        if a == &arb && recs.iter().any(|r| r.rtt.is_some()) {
704                            found = true;
705                        }
706                    });
707                    found
708                };
709                for _ in 0..400 {
710                    if visited() {
711                        break;
712                    }
713                    Delay::new(Duration::from_millis(5)).await;
714                }
715                assert!(visited());
716
717                // callbacks may access the arbiter registry
718                let mut visited = false;
719                System::list_arbiter_pings(|_, _| {
720                    visited = true;
721                    assert!(System::current().0.arbiters.try_lock().is_some());
722                });
723                assert!(visited);
724
725                arb.stop();
726                arb.join().unwrap();
727            });
728    }
729
730    #[test]
731    fn list_arbiters_callback_joins_worker() {
732        System::new("list-join", TestRunner).block_on(async {
733            let sys = System::current();
734            let worker = Arbiter::new();
735            sys.list_arbiters(|arbs| {
736                // the registry is not locked while the callback runs
737                assert!(sys.0.arbiters.try_lock().is_some());
738                let mut arb = arbs.iter().find(|arb| **arb == worker).unwrap().clone();
739                arb.stop();
740                arb.join().unwrap();
741                sys.list_arbiters(|arbs| assert!(!arbs.contains(&worker)));
742            });
743        });
744    }
745}