Skip to main content

ntex_rt/
arbiter.rs

1#![allow(clippy::missing_panics_doc)]
2use std::sync::{Arc, atomic::AtomicBool, atomic::AtomicUsize, atomic::Ordering};
3use std::{any::Any, any::TypeId, cell::RefCell, fmt, mem, panic, rc::Rc, thread};
4
5use async_channel::{Receiver, Sender, unbounded};
6use parking_lot::Mutex;
7
8use crate::metrics::{LatencyMetrics, LatencyStats, start_latency_sampler};
9use crate::{Handle, HashMap, Id, System};
10
11thread_local!(
12    static ADDR: RefCell<Option<Arbiter>> = const { RefCell::new(None) };
13    static STORAGE: RefCell<HashMap<TypeId, Rc<dyn Any>>> = RefCell::new(HashMap::default());
14    static ON_SHUTDOWN: RefCell<Vec<Box<dyn FnOnce()>>> = const { RefCell::new(Vec::new()) };
15);
16
17pub(super) static COUNT: AtomicUsize = AtomicUsize::new(99);
18
19pub(super) enum ArbiterCommand {
20    Stop,
21}
22
23/// An asynchronous execution environment running on one OS thread.
24///
25/// Creating an arbiter starts a thread with its own local event loop. Futures
26/// spawned on that event loop are not required to implement `Send`.
27/// The system's primary arbiter instead runs on the thread driving its
28/// [`SystemRunner`](crate::SystemRunner).
29///
30/// Clones refer to the same arbiter and may be sent to other threads. Use
31/// [`handle`](Self::handle) to submit `Send` futures from another thread.
32pub struct Arbiter(pub(crate) Arc<ArbiterInner>);
33
34type OnCloseStorage = Arc<Mutex<Vec<Box<dyn Fn() + Send + Sync>>>>;
35
36pub(crate) struct ArbiterInner {
37    id: usize,
38    name: Arc<String>,
39    sys_id: usize,
40    hnd: Option<Handle>,
41    pub(crate) sender: Sender<ArbiterCommand>,
42    thread_handle: Mutex<Option<thread::JoinHandle<()>>>,
43    on_stop: OnCloseStorage,
44    running: AtomicBool,
45    pub(crate) latency: Arc<LatencyStats>,
46    #[cfg(target_os = "linux")]
47    tid: i32,
48    #[cfg(windows)]
49    thread: Option<crate::watchdog::ThreadHandle>,
50}
51
52impl fmt::Debug for Arbiter {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "Arbiter({:?})", self.0.name.as_ref())
55    }
56}
57
58impl Clone for Arbiter {
59    fn clone(&self) -> Self {
60        Self(self.0.clone())
61    }
62}
63
64impl Default for Arbiter {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl Arbiter {
71    #[allow(clippy::borrowed_box)]
72    pub(super) fn new_system(id: usize, name: String) -> (Self, ArbiterController) {
73        let (tx, rx) = unbounded();
74
75        let aid = COUNT.fetch_add(1, Ordering::Relaxed);
76        let arb = Arbiter::with_sender(id, aid, Arc::new(name), tx, Arc::default());
77        ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
78        clear_storage();
79
80        (
81            arb,
82            ArbiterController {
83                rx,
84                sys: None,
85                stop: None,
86            },
87        )
88    }
89
90    /// Returns the arbiter running on the current thread.
91    ///
92    /// # Panics
93    ///
94    /// Panics if no arbiter is running on the current thread.
95    pub fn current() -> Arbiter {
96        ADDR.with(|cell| match *cell.borrow() {
97            Some(ref addr) => addr.clone(),
98            None => panic!("Arbiter is not running"),
99        })
100    }
101
102    /// Requests that the arbiter stop its event loop.
103    ///
104    /// This returns without waiting for shutdown; use [`join`](Self::join) to
105    /// wait for a spawned arbiter's thread. To stop the primary arbiter's event
106    /// loop, stop its [`System`] or complete the future driving the system.
107    pub fn stop(&self) {
108        let _ = self.0.sender.try_send(ArbiterCommand::Stop);
109    }
110
111    /// Starts an arbiter on a new thread with an automatically generated name.
112    ///
113    /// Uses the current system's runner and thread configuration.
114    ///
115    /// # Panics
116    ///
117    /// Panics if no current system is installed or the arbiter thread cannot
118    /// be started.
119    pub fn new() -> Arbiter {
120        let id = COUNT.fetch_add(1, Ordering::Relaxed);
121        Arbiter::start(id, format!("{}:arb:{}", System::current().name(), id), None)
122    }
123
124    /// Starts an arbiter on a new thread with the specified name.
125    ///
126    /// Uses the current system's runner and thread configuration.
127    ///
128    /// # Panics
129    ///
130    /// Panics if no current system is installed or the arbiter thread cannot
131    /// be started.
132    pub fn with_name(name: String) -> Arbiter {
133        Arbiter::start(COUNT.fetch_add(1, Ordering::Relaxed), name, None)
134    }
135
136    /// Starts an arbiter on a new thread with the specified name.
137    ///
138    /// `init` runs on the new thread before the arbiter's runtime is created,
139    /// for example to set the thread's CPU affinity, so that the runtime's
140    /// memory is allocated on the NUMA node of that CPU.
141    /// The new thread has no current system or arbiter during `init`, and its
142    /// arbiter storage is cleared after `init` returns.
143    ///
144    /// # Panics
145    ///
146    /// Panics if no current system is installed, the thread cannot be started,
147    /// or initialization fails, including a panic in `init`.
148    pub fn with_init<F>(name: String, init: F) -> Arbiter
149    where
150        F: FnOnce() + Send + 'static,
151    {
152        Arbiter::start(
153            COUNT.fetch_add(1, Ordering::Relaxed),
154            name,
155            Some(Box::new(init)),
156        )
157    }
158
159    fn start(id: usize, name: String, init: Option<Box<dyn FnOnce() + Send>>) -> Arbiter {
160        let sys = System::current();
161        let name2 = Arc::new(name.clone());
162        let config = sys.config();
163        let (arb_tx, arb_rx) = unbounded();
164
165        let builder = if sys.config().stack_size > 0 {
166            thread::Builder::new()
167                .name(name)
168                .stack_size(sys.config().stack_size)
169        } else {
170            thread::Builder::new().name(name)
171        };
172
173        let name = name2.clone();
174        let sys_id = sys.id();
175        let (arb_hnd_tx, arb_hnd_rx) = oneshot::channel();
176
177        let handle = builder
178            .spawn(move || {
179                if let Some(init) = init {
180                    init();
181                }
182                let name3 = name2.clone();
183                log::info!("Starting {name3:?} arbiter");
184
185                let sys2 = sys.clone();
186                let (stop, stop_rx) = oneshot::channel();
187                clear_storage();
188
189                // `block_on` finalizes the arbiter once its event loop exits
190                let result = crate::driver::block_on(config.runner.as_ref(), async move {
191                    let arb = Arbiter::with_sender(sys_id.0, id, name2, arb_tx, Arc::default());
192                    sys.register_arbiter(arb.clone());
193                    // `Arbiter::latency()` is available once the arbiter is returned
194                    start_latency_sampler(&arb.0.latency, config.latency_interval);
195                    arb_hnd_tx
196                        .send(arb.clone())
197                        .expect("Controller thread has gone");
198
199                    // start arbiter controller
200                    crate::spawn_detached(
201                        ArbiterController {
202                            sys: None,
203                            stop: Some(stop),
204                            rx: arb_rx,
205                        }
206                        .run(sys),
207                    );
208                    ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
209
210                    // run loop
211                    let _ = stop_rx.await;
212                });
213
214                let cleanup = || {
215                    // unregister arbiter
216                    sys2.unregister_arbiter(Id(id));
217                    // skipped by `block_on` if the event loop panicked
218                    run_shutdown_callbacks();
219                    // SAFETY: The event loop and shutdown callbacks have ended, so storage borrows are released.
220                    unsafe {
221                        remove_all_items();
222                    }
223                };
224                if result.is_err() {
225                    crate::abort_on_panic(cleanup);
226                } else {
227                    cleanup();
228                }
229
230                if let Err(e) = result {
231                    log::error!("Arbiter {name3:?} has panicked.");
232                    panic::resume_unwind(e);
233                }
234                log::info!("Arbiter {name3:?} has stopped");
235            })
236            .unwrap_or_else(|err| panic!("Cannot spawn an arbiter's thread {name:?}: {err:?}"));
237
238        let arb = arb_hnd_rx.recv().expect("Could not start new arbiter");
239        *arb.0.thread_handle.lock() = Some(handle);
240        arb
241    }
242
243    fn with_sender(
244        sys_id: usize,
245        id: usize,
246        name: Arc<String>,
247        sender: Sender<ArbiterCommand>,
248        on_stop: OnCloseStorage,
249    ) -> Self {
250        #[cfg(feature = "tokio")]
251        let hnd = { Handle::new() };
252
253        #[cfg(feature = "compio")]
254        let hnd = { Handle::new() };
255
256        #[cfg(all(not(feature = "compio"), not(feature = "tokio")))]
257        let hnd = { Handle::current() };
258
259        Self(Arc::new(ArbiterInner {
260            id,
261            sys_id,
262            name,
263            sender,
264            on_stop,
265            hnd: Some(hnd),
266            thread_handle: Mutex::new(None),
267            running: AtomicBool::new(true),
268            latency: Arc::default(),
269            #[cfg(target_os = "linux")]
270            #[allow(clippy::cast_possible_truncation)]
271            // SAFETY: `SYS_gettid` takes no pointer arguments and returns this thread's ID.
272            tid: unsafe { libc::syscall(libc::SYS_gettid) } as i32,
273            #[cfg(windows)]
274            thread: crate::watchdog::ThreadHandle::current(),
275        }))
276    }
277
278    /// Returns the arbiter identifier.
279    pub fn id(&self) -> Id {
280        Id(self.0.id)
281    }
282
283    #[cfg(target_os = "linux")]
284    /// TID of the arbiter
285    pub(crate) fn tid(&self) -> i32 {
286        self.0.tid
287    }
288
289    #[cfg(windows)]
290    /// Handle of the arbiter thread
291    pub(crate) fn thread(&self) -> Option<&crate::watchdog::ThreadHandle> {
292        self.0.thread.as_ref()
293    }
294
295    /// Returns the arbiter name.
296    pub fn name(&self) -> &str {
297        self.0.name.as_ref()
298    }
299
300    #[inline]
301    /// Returns a handle to the arbiter's runtime.
302    pub fn handle(&self) -> &Handle {
303        self.0.hnd.as_ref().unwrap()
304    }
305
306    /// Returns the arbiter's scheduling latency.
307    ///
308    /// Returns `None` if latency sampling is disabled, see
309    /// [`Builder::latency_interval`](crate::Builder::latency_interval).
310    pub fn latency(&self) -> Option<LatencyMetrics> {
311        self.0.latency.metrics()
312    }
313
314    #[inline]
315    /// Returns whether the arbiter is running.
316    ///
317    /// A stop request does not change this immediately. The flag is cleared
318    /// after the event loop exits, before [`on_stop`](Self::on_stop) callbacks.
319    pub fn is_running(&self) -> bool {
320        self.0.running.load(Ordering::Relaxed)
321    }
322
323    /// Returns a value from thread-local arbiter storage, inserting it if absent.
324    ///
325    /// Values are keyed by type and shared with [`get_item`], [`set_item`], and
326    /// [`with_item`] on the calling thread. Existing values are cloned.
327    /// The initializer runs while storage is mutably borrowed and must not
328    /// reenter these storage APIs.
329    ///
330    /// If the storage has already been destroyed because the thread is
331    /// exiting, the value returned by `f` is not stored.
332    pub fn get_value<T, F>(f: F) -> T
333    where
334        T: Clone + 'static,
335        F: FnOnce() -> T,
336    {
337        let mut f = Some(f);
338        STORAGE
339            .try_with(|cell| {
340                let mut st = cell.borrow_mut();
341                if let Some(boxed) = st.get(&TypeId::of::<T>())
342                    && let Some(val) = (&**boxed as &(dyn Any + 'static)).downcast_ref::<T>()
343                {
344                    return val.clone();
345                }
346                let val = (f.take().unwrap())();
347                st.insert(TypeId::of::<T>(), Rc::new(val.clone()));
348                val
349            })
350            .unwrap_or_else(|_| (f.take().unwrap())())
351    }
352
353    /// Registers a callback to run when the current thread's arbiter shuts down.
354    ///
355    /// Callbacks run once, in registration order, on the arbiter's thread when
356    /// its stop is requested, by [`Arbiter::stop()`] or [`System::stop()`],
357    /// while the event loop is still running. An arbiter that ends without a
358    /// stop request, such as one driven by
359    /// [`SystemRunner::block_on()`](crate::SystemRunner::block_on), runs them
360    /// once its event loop has exited instead. A callback registered by
361    /// another callback runs in the same shutdown.
362    ///
363    /// If the thread is exiting and its thread-local storage has already been
364    /// destroyed, `f` is dropped without running. If `f` panics after the
365    /// event loop panicked, the process is aborted.
366    pub fn on_shutdown<F>(f: F)
367    where
368        F: FnOnce() + 'static,
369    {
370        let f: Box<dyn FnOnce()> = Box::new(f);
371        let _ = ON_SHUTDOWN.try_with(move |cell| cell.borrow_mut().push(f));
372    }
373
374    #[must_use]
375    /// Adds a callback to run after the arbiter stops.
376    ///
377    /// The callback runs on the arbiter thread after its event loop exits and
378    /// its running flag is cleared. Register before shutdown; registration after
379    /// finalization does not invoke the callback.
380    ///
381    /// If `f` panics after the event loop panicked, the process is aborted.
382    pub fn on_stop<F>(self, f: F) -> Self
383    where
384        F: Fn() + Send + Sync + 'static,
385    {
386        self.0.on_stop.lock().push(Box::new(f));
387        self
388    }
389
390    /// Returns the arbiter of the current thread, if any.
391    pub(crate) fn try_current() -> Option<Arbiter> {
392        ADDR.try_with(|cell| cell.borrow().clone()).ok().flatten()
393    }
394
395    /// Marks the arbiter as stopped and runs its `on_stop` callbacks,
396    /// called once its event loop has exited.
397    pub(crate) fn finalize(&self) {
398        self.0.running.store(false, Ordering::Relaxed);
399        let on_stop = mem::take(&mut *self.0.on_stop.lock());
400        // a panic while unwinding aborts the process
401        for f in on_stop {
402            f();
403        }
404    }
405
406    /// Waits for the arbiter's thread to stop.
407    ///
408    /// This returns immediately for an arbiter that does not own a thread
409    /// handle, including the system's primary arbiter. The join handle is shared
410    /// by all clones and consumed by the first call. This does not request a stop.
411    ///
412    /// # Errors
413    ///
414    /// Returns the panic payload if the arbiter thread panicked.
415    ///
416    /// # Panics
417    ///
418    /// May panic when attempting to join the current thread.
419    pub fn join(&mut self) -> thread::Result<()> {
420        if let Some(thread_handle) = self.0.thread_handle.lock().take() {
421            thread_handle.join()
422        } else {
423            Ok(())
424        }
425    }
426}
427
428impl Eq for Arbiter {}
429
430impl PartialEq for Arbiter {
431    fn eq(&self, other: &Self) -> bool {
432        self.0.id == other.0.id && self.0.sys_id == other.0.sys_id
433    }
434}
435
436pub(crate) struct ArbiterController {
437    sys: Option<System>,
438    rx: Receiver<ArbiterCommand>,
439    stop: Option<oneshot::Sender<i32>>,
440}
441
442impl ArbiterController {
443    pub(super) async fn run(mut self, sys: System) {
444        self.sys = Some(sys);
445        while let Ok(ArbiterCommand::Stop) = self.rx.recv().await {
446            // the system arbiter has no `stop`, `System::stop()`
447            // runs its callbacks
448            if let Some(stop) = self.stop.take() {
449                run_shutdown_callbacks();
450                let _ = stop.send(0);
451            }
452        }
453    }
454}
455
456/// Runs the callbacks registered with [`Arbiter::on_shutdown()`], including
457/// ones registered while they run.
458pub(crate) fn run_shutdown_callbacks() {
459    loop {
460        let callbacks = ON_SHUTDOWN
461            .try_with(|cell| mem::take(&mut *cell.borrow_mut()))
462            .unwrap_or_default();
463        if callbacks.is_empty() {
464            break;
465        }
466        for f in callbacks {
467            f();
468        }
469    }
470}
471
472/// Inserts a value into the current arbiter's thread-local storage.
473///
474/// Values are keyed by type. Replaces and drops any previous value of type `T`;
475/// the previous value is dropped after releasing the storage borrow.
476///
477/// If the storage has already been destroyed because the thread is
478/// exiting, the value is dropped.
479pub fn set_item<T: 'static>(item: T) {
480    let item: Rc<dyn Any> = Rc::new(item);
481    let old = STORAGE
482        .try_with(move |cell| cell.borrow_mut().insert(TypeId::of::<T>(), item))
483        .ok()
484        .flatten();
485    drop(old);
486}
487
488/// Returns a cloned value from the current arbiter's thread-local storage.
489///
490/// Returns `None` if no value of type `T` is stored, or if the storage has
491/// already been destroyed because the thread is exiting.
492pub fn get_item<T: Clone + 'static>() -> Option<T> {
493    STORAGE
494        .try_with(move |cell| {
495            cell.borrow()
496                .get(&TypeId::of::<T>())
497                .and_then(|boxed| boxed.downcast_ref())
498                .cloned()
499        })
500        .ok()
501        .flatten()
502}
503
504/// Provides access to a value in the current arbiter's thread-local storage.
505///
506/// A default value is inserted if the requested type is not already present.
507/// The callback holds a reference to that value independently of the storage,
508/// so it may replace or remove stored values without invalidating its argument.
509/// If the storage has already been destroyed because the thread is exiting,
510/// `f` receives a temporary default value that is not stored.
511pub fn with_item<T: Default + 'static, F, R>(f: F) -> R
512where
513    F: FnOnce(&T) -> R,
514{
515    // `f` holds its own reference, so the value stays alive even if `f`
516    // replaces or removes it
517    let val = STORAGE
518        .try_with(|cell| {
519            let existing = cell.borrow().get(&TypeId::of::<T>()).cloned();
520            existing.unwrap_or_else(|| {
521                let val: Rc<dyn Any> = Rc::new(T::default());
522                cell.borrow_mut().insert(TypeId::of::<T>(), val.clone());
523                val
524            })
525        })
526        .ok();
527    match val {
528        Some(val) => f(val.downcast_ref::<T>().unwrap()),
529        None => f(&T::default()),
530    }
531}
532
533#[doc(hidden)]
534/// Remove all items from storage.
535///
536/// # Safety
537///
538/// All outstanding calls to [`with_item`] must have completed.
539pub unsafe fn remove_all_items() {
540    clear_storage();
541    System::remove_current();
542}
543
544/// Removes all items from the storage.
545///
546/// Each item is dropped outside of the storage borrow, so that its destructor
547/// may access the storage. Items it inserts are removed too.
548fn clear_storage() {
549    let _ = STORAGE.try_with(|cell| {
550        loop {
551            let mut items = cell.borrow_mut();
552            let Some(key) = items.keys().next().copied() else {
553                break;
554            };
555            let item = items.remove(&key);
556            drop(items);
557            drop(item);
558        }
559    });
560}
561
562#[cfg(test)]
563#[path = "../tests/unit/arbiter.rs"]
564mod tests;