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, TaskCallbacks};
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 [`System`] or [`Arbiter`].
28///
29/// System and arbiter identifiers use separate sequences.
30#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
31pub struct Id(pub(crate) usize);
32
33/// Runtime manager for a group of arbiter threads.
34///
35/// A system stores runtime configuration, manages arbiters, dispatches process
36/// signals, and owns the blocking thread pool.
37///
38/// Clones share the same system and may be sent to other threads. Merely holding
39/// a handle does not install that system as the current system on another thread.
40pub struct System(pub(crate) Arc<SystemInner>);
41
42pub(crate) struct SystemInner {
43    id: usize,
44    arbiter: Arbiter,
45    pub(crate) config: SystemConfig,
46    sender: Sender<SystemCommand>,
47    receiver: Receiver<SystemCommand>,
48    storage: RwLock<HashMap<TypeId, Box<dyn Any + Sync + Send>>>,
49    arbiters: Mutex<Arbiters>,
50    signals: AtomicBool,
51    pool: ThreadPool,
52}
53
54/// Configuration shared by a running [`System`].
55///
56/// Obtain a clone with [`System::config`] and reuse it with
57/// [`System::with_config`] or [`Builder::build_with`]. Signal and panic-handling
58/// settings are not part of this configuration.
59#[derive(Clone)]
60pub struct SystemConfig {
61    pub(super) name: String,
62    pub(super) stack_size: usize,
63    pub(super) ping_interval: usize,
64    pub(super) ping_threshold: usize,
65    pub(super) latency_interval: usize,
66    pub(super) pool_limit: usize,
67    pub(super) pool_recv_timeout: Duration,
68    pub(super) timer_resolution: u8,
69    pub(super) testing: bool,
70    pub(super) task_callbacks: Option<fn() -> TaskCallbacks>,
71    pub(super) runner: Arc<dyn Runner>,
72}
73
74thread_local!(
75    static CURRENT: RefCell<Option<System>> = const { RefCell::new(None) };
76);
77
78impl Clone for System {
79    fn clone(&self) -> Self {
80        Self(self.0.clone())
81    }
82}
83
84impl SystemInner {
85    /// Returns the registered arbiters.
86    pub(crate) fn arbiters(&self) -> Vec<Arbiter> {
87        self.arbiters.lock().list.clone()
88    }
89}
90
91impl System {
92    /// Constructs new system and sets it as current
93    pub(super) fn start(config: SystemConfig) -> (Self, oneshot::Receiver<i32>) {
94        let id = SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst);
95        let (sender, receiver) = unbounded();
96
97        let pool = ThreadPool::new(&config.name, config.pool_limit, config.pool_recv_timeout);
98        let (arbiter, controller) = Arbiter::new_system(id, config.name.clone());
99
100        let mut arbiters = Arbiters::default();
101        arbiters.all.insert(arbiter.id(), arbiter.clone());
102        arbiters.list.push(arbiter.clone());
103
104        let sys = System(Arc::new(SystemInner {
105            id,
106            config,
107            arbiter,
108            sender,
109            receiver,
110            pool,
111            arbiters: Mutex::new(arbiters),
112            storage: RwLock::new(HashMap::default()),
113            signals: AtomicBool::new(false),
114        }));
115        System::set_current(sys.clone());
116
117        let (stop_tx, stop) = oneshot::channel();
118
119        // system support tasks
120        crate::spawn_detached(SystemSupport::new(&sys, stop_tx).run());
121        crate::spawn_detached(controller.run(sys.clone()));
122
123        (sys, stop)
124    }
125
126    /// Creates a builder for a system with a customized runtime.
127    ///
128    /// See [`Builder`] for the available configuration options.
129    pub fn build() -> Builder {
130        Builder::new()
131    }
132
133    #[allow(clippy::new_ret_no_self)]
134    /// Creates a system runner with the specified name and runtime runner.
135    ///
136    /// The system is not started until the returned runner is driven.
137    pub fn new<R: Runner>(name: &str, runner: R) -> SystemRunner {
138        Self::build().name(name).build(runner)
139    }
140
141    #[allow(clippy::new_ret_no_self)]
142    /// Creates a system runner from an existing configuration.
143    ///
144    /// Replaces the configuration's name with `name`. Signal and panic handling
145    /// are disabled for the new runner. The system starts only when it is driven.
146    pub fn with_config(name: &str, mut config: SystemConfig) -> SystemRunner {
147        name.clone_into(&mut config.name);
148        Self::build().name(name).build_with(config)
149    }
150
151    /// Returns the system running on the current thread.
152    ///
153    /// # Panics
154    ///
155    /// Panics if no system is running on the current thread.
156    pub fn current() -> System {
157        CURRENT.with(|cell| match *cell.borrow() {
158            Some(ref sys) => sys.clone(),
159            None => panic!("System is not running"),
160        })
161    }
162
163    /// Returns the system running on the current thread, if one exists.
164    pub fn try_current() -> Option<System> {
165        CURRENT.with(|cell| cell.borrow().as_ref().map(Clone::clone))
166    }
167
168    /// Set current running system
169    #[doc(hidden)]
170    pub fn set_current(sys: System) {
171        crate::task::set_callbacks(sys.0.config.task_callbacks.map(|f| f()));
172        CURRENT.with(|s| {
173            *s.borrow_mut() = Some(sys);
174        });
175    }
176
177    pub(crate) fn register_arbiter(&self, arb: Arbiter) {
178        crate::task::set_callbacks(self.0.config.task_callbacks.map(|f| f()));
179        CURRENT.with(|s| {
180            *s.borrow_mut() = Some(self.clone());
181        });
182        let mut arbiters = self.0.arbiters.lock();
183        arbiters.all.insert(arb.id(), arb.clone());
184        arbiters.list.push(arb);
185    }
186
187    pub(crate) fn unregister_arbiter(&self, id: Id) {
188        crate::task::set_callbacks(None);
189        CURRENT.with(|s| {
190            *s.borrow_mut() = None;
191        });
192        let mut arbiters = self.0.arbiters.lock();
193        if let Some(hnd) = arbiters.all.remove(&id) {
194            for (idx, arb) in arbiters.list.iter().enumerate() {
195                if &hnd == arb {
196                    arbiters.list.remove(idx);
197                    break;
198                }
199            }
200        }
201    }
202
203    pub(super) fn remove_current() {
204        crate::task::set_callbacks(None);
205        CURRENT.with(|cell| {
206            cell.borrow_mut().take();
207        });
208    }
209
210    /// Unregisters all arbiters of the system.
211    pub(super) fn take_arbiters(&self) -> Vec<Arbiter> {
212        let mut arbiters = self.0.arbiters.lock();
213        arbiters.all.clear();
214        std::mem::take(&mut arbiters.list)
215    }
216
217    /// Stops and unregisters all arbiters of the system.
218    pub(super) fn stop_arbiters(&self) {
219        for arb in self.take_arbiters() {
220            arb.stop();
221        }
222    }
223
224    /// Returns the system identifier.
225    pub fn id(&self) -> Id {
226        Id(self.0.id)
227    }
228
229    /// Returns the system name.
230    pub fn name(&self) -> &str {
231        &self.0.config.name
232    }
233
234    /// Requests that the system stop with exit code `0`.
235    ///
236    /// This does not wait for shutdown. See [`stop_with_code`](Self::stop_with_code).
237    pub fn stop(&self) {
238        self.stop_with_code(0);
239    }
240
241    /// Requests that the system stop with the specified exit code.
242    ///
243    /// The request is processed asynchronously on the primary arbiter, which
244    /// asks all registered arbiters to stop. [`SystemRunner::run`] reports
245    /// nonzero codes as errors. A runner awaiting a user future with
246    /// [`SystemRunner::block_on`] still waits for that future to complete.
247    pub fn stop_with_code(&self, code: i32) {
248        let _ = self.0.sender.try_send(SystemCommand::Exit(code));
249    }
250
251    /// Returns whether this system owns process signal handling.
252    pub fn signals(&self) -> bool {
253        self.0.signals.load(Ordering::Relaxed)
254    }
255
256    /// Enables process signal handling.
257    ///
258    /// Signals are handled by one system at a time, this has no effect while
259    /// another system handles signals. Call this on the system's primary
260    /// arbiter thread, where its signal-delivery task and shutdown state live.
261    pub fn enable_signals(&self) {
262        if !self.signals() && crate::signals::start(self) {
263            self.0.signals.store(true, Ordering::Relaxed);
264        }
265    }
266
267    /// Disables process signal handling.
268    pub fn disable_signals(&self) {
269        if self.signals() {
270            crate::signals::stop(self);
271            self.0.signals.store(false, Ordering::Relaxed);
272        }
273    }
274
275    /// Returns the system's primary arbiter.
276    pub fn arbiter(&self) -> Arbiter {
277        self.0.arbiter.clone()
278    }
279
280    /// Provides access to all arbiters registered with this system.
281    ///
282    /// The callback receives a snapshot, including the primary arbiter while it
283    /// remains registered. It runs without the registry lock held, so it may
284    /// create, stop, join, or enumerate arbiters. This method may be called from
285    /// any thread.
286    pub fn list_arbiters<F, R>(&self, f: F) -> R
287    where
288        F: FnOnce(&[Arbiter]) -> R,
289    {
290        f(&self.0.arbiters())
291    }
292
293    /// Visits the latest ping records for each registered arbiter.
294    ///
295    /// This method should be called from the thread where the system has been initialized,
296    /// typically the "main" thread, because records are thread-local. Records
297    /// are stored newest first, with at most ten per arbiter before callbacks
298    /// modify them. Arbiters without records are not visited.
299    ///
300    /// The callback may modify the stored records and runs without the arbiter
301    /// registry lock, but must not call this method recursively.
302    ///
303    /// # Panics
304    ///
305    /// Panics if no current system is installed or if called recursively.
306    pub fn list_arbiter_pings<F>(mut f: F)
307    where
308        F: FnMut(&Arbiter, &mut VecDeque<PingRecord>),
309    {
310        PINGS.with(|pings| {
311            let mut p = pings.borrow_mut();
312            let sys = System::current();
313            let arbiters: Vec<_> = {
314                let arbiters = sys.0.arbiters.lock();
315                p.keys()
316                    .filter_map(|id| arbiters.all.get(id).map(|arb| (*id, arb.clone())))
317                    .collect()
318            };
319
320            // callbacks run without the registry lock
321            for (id, arb) in &arbiters {
322                if let Some(recs) = p.get_mut(id) {
323                    f(arb, recs);
324                }
325            }
326        });
327    }
328
329    #[doc(hidden)]
330    /// Set arbiter latency callback.
331    ///
332    /// This callback is called with the backtrace of a stalled arbiter, see
333    /// [`Builder::ping_threshold`]. Backtraces are captured on Linux and
334    /// Windows. The provided backtrace is not resolved. The location is the
335    /// spawn location of the task that blocks the arbiter, see
336    /// [`Handle::current_task`].
337    pub fn set_latency_callback<F>(f: F)
338    where
339        F: Fn(ntex_error::Backtrace, Option<&'static std::panic::Location<'static>>)
340            + Send
341            + Sync
342            + 'static,
343    {
344        crate::watchdog::set_latency_callback(Arc::new(f));
345    }
346
347    /// Returns a clone of the system configuration.
348    pub fn config(&self) -> SystemConfig {
349        self.0.config.clone()
350    }
351
352    #[inline]
353    /// Returns a runtime handle for the primary arbiter.
354    pub fn handle(&self) -> Handle {
355        self.arbiter().handle().clone()
356    }
357
358    /// Returns whether the system is configured for testing.
359    pub fn testing(&self) -> bool {
360        self.0.config.testing()
361    }
362
363    #[inline]
364    /// Returns the timer wheel resolution in milliseconds.
365    ///
366    /// See [`Builder::timer_resolution`].
367    pub fn timer_resolution(&self) -> u8 {
368        self.0.config.timer_resolution()
369    }
370
371    /// Submits blocking work to this system's thread pool and returns its result future.
372    ///
373    /// This method does not wait for completion. It may be called from any
374    /// thread, regardless of which system is current there.
375    ///
376    /// Dropping the returned future prevents queued work from starting, but
377    /// cannot interrupt work that is already running. Call
378    /// [`BlockingResult::detach`] to let queued work continue even if its
379    /// result is no longer needed.
380    pub fn spawn_blocking<F, R>(&self, f: F) -> BlockingResult<R>
381    where
382        F: FnOnce() -> R + Send + 'static,
383        R: Send + 'static,
384    {
385        self.0.pool.execute(f)
386    }
387
388    /// Returns a snapshot of the blocking thread pool metrics.
389    pub fn pool_metrics(&self) -> PoolMetrics {
390        self.0.pool.metrics()
391    }
392
393    /// Returns a clone of the system-wide value of type `T`, inserting one if absent.
394    ///
395    /// This method acquires a lock on the internal data structure.
396    /// To avoid repeated locking, prefer storing a cloned value in the arbiter's storage.
397    /// Initialization is not atomic: concurrent callers may each run `f` and
398    /// return different values, with the last inserted clone remaining in storage.
399    pub fn get_value<T>(&self, f: impl FnOnce() -> T) -> T
400    where
401        T: Clone + Send + Sync + 'static,
402    {
403        if let Some(boxed) = self.0.storage.read().get(&TypeId::of::<T>())
404            && let Some(val) = (&**boxed as &(dyn Any + 'static)).downcast_ref::<T>()
405        {
406            val.clone()
407        } else {
408            let val = f();
409            self.0
410                .storage
411                .write()
412                .insert(TypeId::of::<T>(), Box::new(val.clone()));
413            val
414        }
415    }
416}
417
418impl SystemConfig {
419    #[inline]
420    /// Returns whether the system is configured for testing.
421    pub fn testing(&self) -> bool {
422        self.testing
423    }
424
425    #[inline]
426    /// Returns the timer wheel resolution in milliseconds.
427    ///
428    /// See [`Builder::timer_resolution`].
429    pub fn timer_resolution(&self) -> u8 {
430        self.timer_resolution
431    }
432
433    #[inline]
434    /// Returns the task callbacks factory, if set.
435    ///
436    /// See [`Builder::task_callbacks`].
437    pub fn task_callbacks(&self) -> Option<fn() -> TaskCallbacks> {
438        self.task_callbacks
439    }
440}
441
442impl fmt::Debug for System {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        f.debug_struct("System")
445            .field("id", &self.0.id)
446            .field("config", &self.0.config)
447            .field("signals", &self.signals())
448            .field("pool", &self.0.pool)
449            .finish()
450    }
451}
452
453impl fmt::Debug for SystemConfig {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        f.debug_struct("SystemConfig")
456            .field("name", &self.name)
457            .field("testing", &self.testing)
458            .field("stack_size", &self.stack_size)
459            .field("timer_resolution", &self.timer_resolution)
460            .field("task_callbacks", &self.task_callbacks)
461            .finish()
462    }
463}
464
465#[derive(Debug)]
466pub(super) enum SystemCommand {
467    Exit(i32),
468}
469
470#[derive(Debug)]
471struct SystemSupport {
472    sys: System,
473    stop: Option<oneshot::Sender<i32>>,
474    commands: Receiver<SystemCommand>,
475    watchdog: Option<Arc<AtomicBool>>,
476}
477
478impl Drop for SystemSupport {
479    fn drop(&mut self) {
480        self.stop_watchdog();
481    }
482}
483
484impl SystemSupport {
485    fn new(sys: &System, stop: oneshot::Sender<i32>) -> Self {
486        Self {
487            sys: sys.clone(),
488            stop: Some(stop),
489            commands: sys.0.receiver.clone(),
490            watchdog: None,
491        }
492    }
493
494    fn stop_watchdog(&mut self) {
495        if let Some(stop) = self.watchdog.take() {
496            stop.store(true, Ordering::Relaxed);
497        }
498    }
499
500    async fn run(mut self) {
501        crate::metrics::start_latency_sampler(
502            &self.sys.0.arbiter.0.latency,
503            self.sys.0.config.latency_interval,
504        );
505        self.watchdog = crate::watchdog::start(&self.sys);
506        if self.sys.0.config.ping_interval != 0 {
507            crate::spawn_detached(ping_arbiters(self.sys.clone()));
508        }
509
510        loop {
511            match self.commands.recv().await {
512                Ok(SystemCommand::Exit(code)) => {
513                    log::debug!("Stopping system with {code} code");
514                    self.stop_watchdog();
515                    self.sys.stop_arbiters();
516
517                    crate::arbiter::run_shutdown_callbacks();
518
519                    // stop event loop
520                    if let Some(stop) = self.stop.take() {
521                        let _ = stop.send(code);
522                    }
523                }
524                Err(_) => {
525                    log::debug!("System stopped");
526                    return;
527                }
528            }
529        }
530    }
531}
532
533/// A ping sent to an arbiter, as exposed by [`System::list_arbiter_pings`].
534#[derive(Copy, Clone, Debug)]
535pub struct PingRecord {
536    /// Time the ping round started.
537    pub start: Instant,
538    /// Elapsed time when the primary arbiter observed the ping task complete.
539    ///
540    /// `None` means no successful response has been recorded, including when
541    /// the ping is still pending or its task was canceled.
542    pub rtt: Option<Duration>,
543}
544
545async fn ping_arbiters(sys: System) {
546    let interval = Duration::from_millis(sys.0.config.ping_interval as u64);
547
548    loop {
549        // interval between pings
550        Delay::new(interval).await;
551
552        let start = Instant::now();
553        let arbiters = sys.0.arbiters.lock();
554
555        // drop records of stopped arbiters
556        PINGS.with(|pings| {
557            pings
558                .borrow_mut()
559                .retain(|id, _| arbiters.all.contains_key(id));
560        });
561
562        for arb in &arbiters.list {
563            let id = arb.id();
564            let fut = arb.handle().spawn(async move {
565                yield_to().await;
566            });
567
568            PINGS.with(|pings| {
569                let mut p = pings.borrow_mut();
570                let recs = p.entry(id).or_default();
571                recs.push_front(PingRecord { start, rtt: None });
572                recs.truncate(10);
573            });
574
575            crate::spawn_detached(async move {
576                if fut.await.is_ok() {
577                    PINGS.with(|pings| {
578                        // a late pong belongs to its own round
579                        if let Some(recs) = pings.borrow_mut().get_mut(&id)
580                            && let Some(rec) = recs.iter_mut().find(|r| r.start == start)
581                        {
582                            rec.rtt = Some(start.elapsed());
583                        }
584                    });
585                }
586            });
587        }
588    }
589}
590
591async fn yield_to() {
592    use std::task::{Context, Poll};
593
594    struct Yield {
595        completed: bool,
596    }
597
598    impl Future for Yield {
599        type Output = ();
600
601        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
602            if self.completed {
603                return Poll::Ready(());
604            }
605            self.completed = true;
606            cx.waker().wake_by_ref();
607            Poll::Pending
608        }
609    }
610
611    Yield { completed: false }.await;
612}
613
614#[cfg(test)]
615#[path = "../tests/unit/system.rs"]
616mod tests;