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                    unsafe {
220                        remove_all_items();
221                    }
222                };
223                if result.is_err() {
224                    crate::abort_on_panic(cleanup);
225                } else {
226                    cleanup();
227                }
228
229                if let Err(e) = result {
230                    log::error!("Arbiter {name3:?} has panicked.");
231                    panic::resume_unwind(e);
232                }
233                log::info!("Arbiter {name3:?} has stopped");
234            })
235            .unwrap_or_else(|err| panic!("Cannot spawn an arbiter's thread {name:?}: {err:?}"));
236
237        let arb = arb_hnd_rx.recv().expect("Could not start new arbiter");
238        *arb.0.thread_handle.lock() = Some(handle);
239        arb
240    }
241
242    fn with_sender(
243        sys_id: usize,
244        id: usize,
245        name: Arc<String>,
246        sender: Sender<ArbiterCommand>,
247        on_stop: OnCloseStorage,
248    ) -> Self {
249        #[cfg(feature = "tokio")]
250        let hnd = { Handle::new() };
251
252        #[cfg(feature = "compio")]
253        let hnd = { Handle::new() };
254
255        #[cfg(all(not(feature = "compio"), not(feature = "tokio")))]
256        let hnd = { Handle::current() };
257
258        Self(Arc::new(ArbiterInner {
259            id,
260            sys_id,
261            name,
262            sender,
263            on_stop,
264            hnd: Some(hnd),
265            thread_handle: Mutex::new(None),
266            running: AtomicBool::new(true),
267            latency: Arc::default(),
268            #[cfg(target_os = "linux")]
269            #[allow(clippy::cast_possible_truncation)]
270            tid: unsafe { libc::syscall(libc::SYS_gettid) } as i32,
271            #[cfg(windows)]
272            thread: crate::watchdog::ThreadHandle::current(),
273        }))
274    }
275
276    /// Returns the arbiter identifier.
277    pub fn id(&self) -> Id {
278        Id(self.0.id)
279    }
280
281    #[cfg(target_os = "linux")]
282    /// TID of the arbiter
283    pub(crate) fn tid(&self) -> i32 {
284        self.0.tid
285    }
286
287    #[cfg(windows)]
288    /// Handle of the arbiter thread
289    pub(crate) fn thread(&self) -> Option<&crate::watchdog::ThreadHandle> {
290        self.0.thread.as_ref()
291    }
292
293    /// Returns the arbiter name.
294    pub fn name(&self) -> &str {
295        self.0.name.as_ref()
296    }
297
298    #[inline]
299    /// Returns a handle to the arbiter's runtime.
300    pub fn handle(&self) -> &Handle {
301        self.0.hnd.as_ref().unwrap()
302    }
303
304    /// Returns the arbiter's scheduling latency.
305    ///
306    /// Returns `None` if latency sampling is disabled, see
307    /// [`Builder::latency_interval`](crate::Builder::latency_interval).
308    pub fn latency(&self) -> Option<LatencyMetrics> {
309        self.0.latency.metrics()
310    }
311
312    #[inline]
313    /// Returns whether the arbiter is running.
314    ///
315    /// A stop request does not change this immediately. The flag is cleared
316    /// after the event loop exits, before [`on_stop`](Self::on_stop) callbacks.
317    pub fn is_running(&self) -> bool {
318        self.0.running.load(Ordering::Relaxed)
319    }
320
321    /// Returns a value from thread-local arbiter storage, inserting it if absent.
322    ///
323    /// Values are keyed by type and shared with [`get_item`], [`set_item`], and
324    /// [`with_item`] on the calling thread. Existing values are cloned.
325    /// The initializer runs while storage is mutably borrowed and must not
326    /// reenter these storage APIs.
327    ///
328    /// If the storage has already been destroyed because the thread is
329    /// exiting, the value returned by `f` is not stored.
330    pub fn get_value<T, F>(f: F) -> T
331    where
332        T: Clone + 'static,
333        F: FnOnce() -> T,
334    {
335        let mut f = Some(f);
336        STORAGE
337            .try_with(|cell| {
338                let mut st = cell.borrow_mut();
339                if let Some(boxed) = st.get(&TypeId::of::<T>())
340                    && let Some(val) = (&**boxed as &(dyn Any + 'static)).downcast_ref::<T>()
341                {
342                    return val.clone();
343                }
344                let val = (f.take().unwrap())();
345                st.insert(TypeId::of::<T>(), Rc::new(val.clone()));
346                val
347            })
348            .unwrap_or_else(|_| (f.take().unwrap())())
349    }
350
351    /// Registers a callback to run when the current thread's arbiter shuts down.
352    ///
353    /// Callbacks run once, in registration order, on the arbiter's thread when
354    /// its stop is requested, by [`Arbiter::stop()`] or [`System::stop()`],
355    /// while the event loop is still running. An arbiter that ends without a
356    /// stop request, such as one driven by
357    /// [`SystemRunner::block_on()`](crate::SystemRunner::block_on), runs them
358    /// once its event loop has exited instead. A callback registered by
359    /// another callback runs in the same shutdown.
360    ///
361    /// If the thread is exiting and its thread-local storage has already been
362    /// destroyed, `f` is dropped without running. If `f` panics after the
363    /// event loop panicked, the process is aborted.
364    pub fn on_shutdown<F>(f: F)
365    where
366        F: FnOnce() + 'static,
367    {
368        let f: Box<dyn FnOnce()> = Box::new(f);
369        let _ = ON_SHUTDOWN.try_with(move |cell| cell.borrow_mut().push(f));
370    }
371
372    #[must_use]
373    /// Adds a callback to run after the arbiter stops.
374    ///
375    /// The callback runs on the arbiter thread after its event loop exits and
376    /// its running flag is cleared. Register before shutdown; registration after
377    /// finalization does not invoke the callback.
378    ///
379    /// If `f` panics after the event loop panicked, the process is aborted.
380    pub fn on_stop<F>(self, f: F) -> Self
381    where
382        F: Fn() + Send + Sync + 'static,
383    {
384        self.0.on_stop.lock().push(Box::new(f));
385        self
386    }
387
388    /// Returns the arbiter of the current thread, if any.
389    pub(crate) fn try_current() -> Option<Arbiter> {
390        ADDR.try_with(|cell| cell.borrow().clone()).ok().flatten()
391    }
392
393    /// Marks the arbiter as stopped and runs its `on_stop` callbacks,
394    /// called once its event loop has exited.
395    pub(crate) fn finalize(&self) {
396        self.0.running.store(false, Ordering::Relaxed);
397        let on_stop = mem::take(&mut *self.0.on_stop.lock());
398        // a panic while unwinding aborts the process
399        for f in on_stop {
400            f();
401        }
402    }
403
404    /// Waits for the arbiter's thread to stop.
405    ///
406    /// This returns immediately for an arbiter that does not own a thread
407    /// handle, including the system's primary arbiter. The join handle is shared
408    /// by all clones and consumed by the first call. This does not request a stop.
409    ///
410    /// # Errors
411    ///
412    /// Returns the panic payload if the arbiter thread panicked.
413    ///
414    /// # Panics
415    ///
416    /// May panic when attempting to join the current thread.
417    pub fn join(&mut self) -> thread::Result<()> {
418        if let Some(thread_handle) = self.0.thread_handle.lock().take() {
419            thread_handle.join()
420        } else {
421            Ok(())
422        }
423    }
424}
425
426impl Eq for Arbiter {}
427
428impl PartialEq for Arbiter {
429    fn eq(&self, other: &Self) -> bool {
430        self.0.id == other.0.id && self.0.sys_id == other.0.sys_id
431    }
432}
433
434pub(crate) struct ArbiterController {
435    sys: Option<System>,
436    rx: Receiver<ArbiterCommand>,
437    stop: Option<oneshot::Sender<i32>>,
438}
439
440impl ArbiterController {
441    pub(super) async fn run(mut self, sys: System) {
442        self.sys = Some(sys);
443        while let Ok(ArbiterCommand::Stop) = self.rx.recv().await {
444            // the system arbiter has no `stop`, `System::stop()`
445            // runs its callbacks
446            if let Some(stop) = self.stop.take() {
447                run_shutdown_callbacks();
448                let _ = stop.send(0);
449            }
450        }
451    }
452}
453
454/// Runs the callbacks registered with [`Arbiter::on_shutdown()`], including
455/// ones registered while they run.
456pub(crate) fn run_shutdown_callbacks() {
457    loop {
458        let callbacks = ON_SHUTDOWN
459            .try_with(|cell| mem::take(&mut *cell.borrow_mut()))
460            .unwrap_or_default();
461        if callbacks.is_empty() {
462            break;
463        }
464        for f in callbacks {
465            f();
466        }
467    }
468}
469
470/// Inserts a value into the current arbiter's thread-local storage.
471///
472/// Values are keyed by type. Replaces and drops any previous value of type `T`;
473/// the previous value is dropped after releasing the storage borrow.
474///
475/// If the storage has already been destroyed because the thread is
476/// exiting, the value is dropped.
477pub fn set_item<T: 'static>(item: T) {
478    let item: Rc<dyn Any> = Rc::new(item);
479    let old = STORAGE
480        .try_with(move |cell| cell.borrow_mut().insert(TypeId::of::<T>(), item))
481        .ok()
482        .flatten();
483    drop(old);
484}
485
486/// Returns a cloned value from the current arbiter's thread-local storage.
487///
488/// Returns `None` if no value of type `T` is stored, or if the storage has
489/// already been destroyed because the thread is exiting.
490pub fn get_item<T: Clone + 'static>() -> Option<T> {
491    STORAGE
492        .try_with(move |cell| {
493            cell.borrow()
494                .get(&TypeId::of::<T>())
495                .and_then(|boxed| boxed.downcast_ref())
496                .cloned()
497        })
498        .ok()
499        .flatten()
500}
501
502/// Provides access to a value in the current arbiter's thread-local storage.
503///
504/// A default value is inserted if the requested type is not already present.
505/// The callback holds a reference to that value independently of the storage,
506/// so it may replace or remove stored values without invalidating its argument.
507/// If the storage has already been destroyed because the thread is exiting,
508/// `f` receives a temporary default value that is not stored.
509pub fn with_item<T: Default + 'static, F, R>(f: F) -> R
510where
511    F: FnOnce(&T) -> R,
512{
513    // `f` holds its own reference, so the value stays alive even if `f`
514    // replaces or removes it
515    let val = STORAGE
516        .try_with(|cell| {
517            let existing = cell.borrow().get(&TypeId::of::<T>()).cloned();
518            existing.unwrap_or_else(|| {
519                let val: Rc<dyn Any> = Rc::new(T::default());
520                cell.borrow_mut().insert(TypeId::of::<T>(), val.clone());
521                val
522            })
523        })
524        .ok();
525    match val {
526        Some(val) => f(val.downcast_ref::<T>().unwrap()),
527        None => f(&T::default()),
528    }
529}
530
531#[doc(hidden)]
532/// Remove all items from storage.
533///
534/// # Safety
535///
536/// All outstanding calls to [`with_item`] must have completed.
537pub unsafe fn remove_all_items() {
538    clear_storage();
539    System::remove_current();
540}
541
542/// Removes all items from the storage.
543///
544/// Each item is dropped outside of the storage borrow, so that its destructor
545/// may access the storage. Items it inserts are removed too.
546fn clear_storage() {
547    let _ = STORAGE.try_with(|cell| {
548        loop {
549            let mut items = cell.borrow_mut();
550            let Some(key) = items.keys().next().copied() else {
551                break;
552            };
553            let item = items.remove(&key);
554            drop(items);
555            drop(item);
556        }
557    });
558}
559
560#[cfg(test)]
561#[path = "../tests/unit/arbiter.rs"]
562mod tests;