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