Skip to main content

actix_rt/
system.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    future::Future,
5    io,
6    pin::Pin,
7    sync::atomic::{AtomicUsize, Ordering},
8    task::{Context, Poll},
9};
10
11use futures_core::ready;
12use tokio::sync::{mpsc, watch};
13
14use crate::{arbiter::ArbiterHandle, Arbiter};
15
16static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
17
18thread_local!(
19    static CURRENT: RefCell<Option<System>> = const { RefCell::new(None) };
20);
21
22/// A manager for a per-thread distributed async runtime.
23#[derive(Clone, Debug)]
24pub struct System {
25    id: usize,
26    sys_tx: mpsc::UnboundedSender<SystemCommand>,
27
28    /// Handle to the first [Arbiter] that is created with the System.
29    arbiter_handle: ArbiterHandle,
30}
31
32impl System {
33    /// Create a new system.
34    ///
35    /// # Panics
36    /// Panics if underlying Tokio runtime can not be created.
37    #[allow(clippy::new_ret_no_self)]
38    pub fn new() -> SystemRunner {
39        Self::with_tokio_rt(|| {
40            crate::runtime::default_tokio_runtime()
41                .expect("Default Actix (Tokio) runtime could not be created.")
42        })
43    }
44
45    /// Create a new System using the [Tokio Runtime](tokio-runtime) returned from a closure.
46    ///
47    /// The closure may return any type that can be converted into [`Runtime`], such as
48    /// `tokio::runtime::Runtime`, `Arc<tokio::runtime::Runtime>`, or
49    /// `&'static tokio::runtime::Runtime`.
50    ///
51    /// [tokio-runtime]: tokio::runtime::Runtime
52    /// [`Runtime`]: crate::Runtime
53    pub fn with_tokio_rt<F, R>(runtime_factory: F) -> SystemRunner
54    where
55        F: FnOnce() -> R,
56        R: Into<crate::runtime::Runtime>,
57    {
58        let (stop_tx, stop_rx) = watch::channel(None);
59        let (sys_tx, sys_rx) = mpsc::unbounded_channel();
60
61        let rt = runtime_factory().into();
62        let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() });
63        let system = System::construct(sys_tx, sys_arbiter.clone());
64
65        system
66            .tx()
67            .send(SystemCommand::RegisterArbiter(usize::MAX, sys_arbiter))
68            .unwrap();
69
70        // init background system arbiter
71        let sys_ctrl = SystemController::new(sys_rx, stop_tx);
72        rt.spawn(sys_ctrl);
73
74        SystemRunner { rt, stop_rx }
75    }
76}
77
78impl System {
79    /// Constructs new system and registers it on the current thread.
80    pub(crate) fn construct(
81        sys_tx: mpsc::UnboundedSender<SystemCommand>,
82        arbiter_handle: ArbiterHandle,
83    ) -> Self {
84        let sys = System {
85            sys_tx,
86            arbiter_handle,
87            id: SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst),
88        };
89
90        System::set_current(sys.clone());
91
92        sys
93    }
94
95    /// Get current running system.
96    ///
97    /// # Panics
98    /// Panics if no system is registered on the current thread.
99    pub fn current() -> System {
100        CURRENT.with(|cell| match *cell.borrow() {
101            Some(ref sys) => sys.clone(),
102            None => panic!("System is not running"),
103        })
104    }
105
106    /// Try to get current running system.
107    ///
108    /// Returns `None` if no System has been started.
109    ///
110    /// Unlike [`current`](Self::current), this never panics.
111    pub fn try_current() -> Option<System> {
112        CURRENT.with(|cell| cell.borrow().clone())
113    }
114
115    /// Get handle to a the System's initial [Arbiter].
116    pub fn arbiter(&self) -> &ArbiterHandle {
117        &self.arbiter_handle
118    }
119
120    /// Check if there is a System registered on the current thread.
121    pub fn is_registered() -> bool {
122        CURRENT.with(|sys| sys.borrow().is_some())
123    }
124
125    /// Register given system on current thread.
126    #[doc(hidden)]
127    pub fn set_current(sys: System) {
128        CURRENT.with(|cell| {
129            *cell.borrow_mut() = Some(sys);
130        })
131    }
132
133    /// Numeric system identifier.
134    ///
135    /// Useful when using multiple Systems.
136    pub fn id(&self) -> usize {
137        self.id
138    }
139
140    /// Stop the system (with code 0).
141    pub fn stop(&self) {
142        self.stop_with_code(0)
143    }
144
145    /// Stop the system with a given exit code.
146    pub fn stop_with_code(&self, code: i32) {
147        let _ = self.sys_tx.send(SystemCommand::Exit(code));
148    }
149
150    pub(crate) fn tx(&self) -> &mpsc::UnboundedSender<SystemCommand> {
151        &self.sys_tx
152    }
153}
154
155/// Runner that keeps a [System]'s event loop alive until stop message is received.
156#[must_use = "A SystemRunner does nothing unless `run` is called."]
157#[derive(Debug)]
158pub struct SystemRunner {
159    rt: crate::runtime::Runtime,
160    stop_rx: watch::Receiver<Option<i32>>,
161}
162
163impl SystemRunner {
164    /// Starts event loop and will return once [System] is [stopped](System::stop).
165    pub fn run(self) -> io::Result<()> {
166        let exit_code = self.run_with_code()?;
167
168        match exit_code {
169            0 => Ok(()),
170            nonzero => Err(io::Error::other(format!("Non-zero exit code: {nonzero}"))),
171        }
172    }
173
174    /// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
175    pub fn run_with_code(self) -> io::Result<i32> {
176        let SystemRunner { rt, stop_rx, .. } = self;
177
178        // run loop
179        rt.block_on(wait_for_stop(stop_rx))
180    }
181
182    /// Retrieves a reference to the underlying [Actix runtime](crate::Runtime) associated with this
183    /// `SystemRunner` instance.
184    ///
185    /// The Actix runtime is responsible for managing the event loop for an Actix system and
186    /// executing asynchronous tasks. This method provides access to the runtime, allowing direct
187    /// interaction with its features.
188    ///
189    /// In a typical use case, you might need to share the same runtime between different
190    /// parts of your project. For example, some components might require a [`Runtime`] to spawn
191    /// tasks on the same runtime.
192    ///
193    /// Read more in the documentation for [`Runtime`].
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// let system_runner = actix_rt::System::new();
199    /// let actix_runtime = system_runner.runtime();
200    ///
201    /// // Use the runtime to spawn an async task or perform other operations
202    /// ```
203    ///
204    /// # Note
205    ///
206    /// While this method provides an immutable reference to the Actix runtime, which is safe to
207    /// share across threads, be aware that spawning blocking tasks on the Actix runtime could
208    /// potentially impact system performance. This is because the Actix runtime is responsible for
209    /// driving the system, and blocking tasks could delay other tasks in the run loop.
210    ///
211    /// [`Runtime`]: crate::Runtime
212    pub fn runtime(&self) -> &crate::runtime::Runtime {
213        &self.rt
214    }
215
216    /// Returns a future that resolves with the system's exit code when it is stopped.
217    ///
218    /// This can be used to react to a system stop signal while running a future with
219    /// [`SystemRunner::block_on`], such as when coordinating shutdown with `tokio::select!`.
220    ///
221    /// # Examples
222    /// ```no_run
223    /// use std::process::ExitCode;
224    /// use actix_rt::System;
225    ///
226    /// let sys = System::new();
227    /// let stop = sys.stop_future();
228    ///
229    /// let exit = sys.block_on(async move {
230    ///     actix_rt::spawn(async {
231    ///         System::current().stop_with_code(0);
232    ///     });
233    ///
234    ///     let code = stop.await.unwrap_or(1);
235    ///     ExitCode::from(code as u8)
236    /// });
237    ///
238    /// # drop(exit);
239    /// ```
240    pub fn stop_future(&self) -> SystemStop {
241        SystemStop::new(self.stop_rx.clone())
242    }
243
244    /// Splits this runner into its runtime and a future that resolves when the system stops.
245    ///
246    /// After calling this method, [`SystemRunner::run`] and [`SystemRunner::run_with_code`] can no
247    /// longer be used.
248    pub fn into_parts(self) -> (crate::runtime::Runtime, SystemStop) {
249        let SystemRunner { rt, stop_rx } = self;
250        (rt, SystemStop::new(stop_rx))
251    }
252
253    /// Runs the provided future, blocking the current thread until the future completes.
254    #[track_caller]
255    #[inline]
256    pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
257        self.rt.block_on(fut)
258    }
259}
260
261/// Future that resolves with the exit code when a [`System`] is stopped.
262#[must_use = "SystemStop does nothing unless polled or awaited."]
263pub struct SystemStop {
264    inner: Pin<Box<dyn Future<Output = io::Result<i32>> + 'static>>,
265}
266
267impl SystemStop {
268    fn new(stop_rx: watch::Receiver<Option<i32>>) -> Self {
269        Self {
270            inner: Box::pin(wait_for_stop(stop_rx)),
271        }
272    }
273}
274
275impl Future for SystemStop {
276    type Output = io::Result<i32>;
277
278    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
279        self.inner.as_mut().poll(cx)
280    }
281}
282
283async fn wait_for_stop(mut stop_rx: watch::Receiver<Option<i32>>) -> io::Result<i32> {
284    loop {
285        if let Some(code) = *stop_rx.borrow() {
286            return Ok(code);
287        }
288
289        stop_rx.changed().await.map_err(io::Error::other)?;
290    }
291}
292
293#[derive(Debug)]
294pub(crate) enum SystemCommand {
295    Exit(i32),
296    RegisterArbiter(usize, ArbiterHandle),
297    DeregisterArbiter(usize),
298}
299
300/// There is one `SystemController` per [System]. It runs in the background, keeping track of
301/// [Arbiter]s and is able to distribute a system-wide stop command.
302#[derive(Debug)]
303pub(crate) struct SystemController {
304    stop_tx: Option<watch::Sender<Option<i32>>>,
305    cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
306    arbiters: HashMap<usize, ArbiterHandle>,
307}
308
309impl SystemController {
310    pub(crate) fn new(
311        cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
312        stop_tx: watch::Sender<Option<i32>>,
313    ) -> Self {
314        SystemController {
315            cmd_rx,
316            stop_tx: Some(stop_tx),
317            arbiters: HashMap::with_capacity(4),
318        }
319    }
320}
321
322impl Future for SystemController {
323    type Output = ();
324
325    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
326        // process all items currently buffered in channel
327        loop {
328            match ready!(self.cmd_rx.poll_recv(cx)) {
329                // channel closed; no more messages can be received
330                None => return Poll::Ready(()),
331
332                // process system command
333                Some(cmd) => match cmd {
334                    SystemCommand::Exit(code) => {
335                        // stop all arbiters
336                        for arb in self.arbiters.values() {
337                            arb.stop();
338                        }
339
340                        // stop event loop
341                        // will only fire once
342                        if let Some(stop_tx) = self.stop_tx.take() {
343                            let _ = stop_tx.send(Some(code));
344                        }
345                    }
346
347                    SystemCommand::RegisterArbiter(id, arb) => {
348                        self.arbiters.insert(id, arb);
349                    }
350
351                    SystemCommand::DeregisterArbiter(id) => {
352                        self.arbiters.remove(&id);
353                    }
354                },
355            }
356        }
357    }
358}