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;