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