Skip to main content

openraft_rt/
async_runtime.rs

1//! `async` runtime interface.
2//!
3//! `async` runtime is an abstraction over different asynchronous runtimes, such as `tokio`,
4//! `async-std`, etc.
5
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::future::Future;
9use std::io;
10use std::time::Duration;
11
12use openraft_macros::since;
13
14use crate::Instant;
15use crate::Mpsc;
16use crate::MpscReceiver;
17use crate::Mutex;
18use crate::Oneshot;
19use crate::OptionalSend;
20use crate::OptionalSync;
21use crate::TryRecvError;
22use crate::Watch;
23
24/// A trait defining interfaces with an asynchronous runtime.
25///
26/// The intention of this trait is to allow an application using this crate to bind an asynchronous
27/// runtime that suits it the best.
28///
29/// Some additional related functions are also exposed by this trait.
30///
31/// ## Note
32///
33/// The default asynchronous runtime is `tokio`.
34pub trait AsyncRuntime: Debug + OptionalSend + OptionalSync + 'static {
35    /// The error type of [`Self::JoinHandle`].
36    type JoinError: Debug + Display + OptionalSend;
37
38    /// The return type of [`Self::spawn`].
39    type JoinHandle<T: OptionalSend + 'static>: Future<Output = Result<T, Self::JoinError>>
40        + OptionalSend
41        + OptionalSync
42        + Unpin;
43
44    /// The type that enables the user to sleep in an asynchronous runtime.
45    type Sleep: Future<Output = ()> + OptionalSend + OptionalSync;
46
47    /// A measurement of a monotonically non-decreasing clock.
48    type Instant: Instant;
49
50    /// The timeout error type.
51    type TimeoutError: Debug + Display + OptionalSend;
52
53    /// The timeout type used by [`Self::timeout`] and [`Self::timeout_at`] that enables the user
54    /// to await the outcome of a [`Future`].
55    type Timeout<R, T: Future<Output = R> + OptionalSend>: Future<Output = Result<R, Self::TimeoutError>> + OptionalSend;
56
57    /// Type of thread-local random number generator.
58    type ThreadLocalRng: rand::Rng;
59
60    /// Spawn a new task.
61    #[track_caller]
62    fn spawn<T>(future: T) -> Self::JoinHandle<T::Output>
63    where
64        T: Future + OptionalSend + 'static,
65        T::Output: OptionalSend + 'static;
66
67    /// Wait until `duration` has elapsed.
68    #[track_caller]
69    fn sleep(duration: Duration) -> Self::Sleep;
70
71    /// Wait until `deadline` is reached.
72    #[track_caller]
73    fn sleep_until(deadline: Self::Instant) -> Self::Sleep;
74
75    /// Require a [`Future`] to complete before the specified duration has elapsed.
76    #[track_caller]
77    fn timeout<R, F: Future<Output = R> + OptionalSend>(duration: Duration, future: F) -> Self::Timeout<R, F>;
78
79    /// Require a [`Future`] to complete before the specified instant in time.
80    #[track_caller]
81    fn timeout_at<R, F: Future<Output = R> + OptionalSend>(deadline: Self::Instant, future: F) -> Self::Timeout<R, F>;
82
83    /// Check if the [`Self::JoinError`] is `panic`.
84    #[track_caller]
85    fn is_panic(join_error: &Self::JoinError) -> bool;
86
87    /// Get the random number generator to use for generating random numbers.
88    ///
89    /// # Note
90    ///
91    /// This is a per-thread instance, which cannot be shared across threads or
92    /// sent to another thread.
93    #[track_caller]
94    fn thread_rng() -> Self::ThreadLocalRng;
95
96    /// The bounded MPSC channel implementation.
97    type Mpsc: Mpsc;
98
99    /// The watch channel implementation.
100    type Watch: Watch;
101
102    /// The oneshot channel implementation.
103    type Oneshot: Oneshot;
104
105    /// The async mutex implementation.
106    type Mutex<T: OptionalSend + 'static>: Mutex<T>;
107
108    /// Create a new runtime instance for testing purposes.
109    ///
110    /// **Note**: This method is primarily intended for testing and is not used by Openraft
111    /// internally. In production applications, the runtime should be created and managed
112    /// by the application itself, with Openraft running within that runtime.
113    ///
114    /// # Arguments
115    ///
116    /// * `threads` - Number of worker threads. Multi-threaded runtimes (like Tokio) will use this
117    ///   value; single-threaded runtimes (like Monoio, Compio) may ignore it.
118    fn new(threads: usize) -> Self;
119
120    /// Run a future to completion on this runtime.
121    ///
122    /// This runs synchronously on the current thread, so `Send` is not required.
123    fn block_on<F, T>(&mut self, future: F) -> T
124    where
125        F: Future<Output = T>,
126        T: OptionalSend;
127
128    /// Convenience method: create a runtime and run the future to completion.
129    ///
130    /// Creates a runtime with default configuration (8 threads) and runs the future.
131    /// For simple cases where you don't need to reuse the runtime.
132    /// If you need to run multiple futures, consider using [`Self::new`] and
133    /// [`Self::block_on`] directly.
134    ///
135    /// This runs synchronously on the current thread, so `Send` is not required.
136    fn run<F, T>(future: F) -> T
137    where
138        Self: Sized,
139        F: Future<Output = T>,
140        T: OptionalSend,
141    {
142        Self::new(8).block_on(future)
143    }
144
145    /// Run a blocking function on a separate thread.
146    ///
147    /// The default implementation spawns a new OS thread for each call.
148    /// Runtime implementations may override this with their own thread pool
149    /// (e.g., tokio's `spawn_blocking`) for better resource management.
150    fn spawn_blocking<F, T>(f: F) -> impl Future<Output = Result<T, io::Error>> + Send
151    where
152        F: FnOnce() -> T + Send + 'static,
153        T: Send + 'static,
154    {
155        let (tx, rx) = futures_channel::oneshot::channel();
156        std::thread::spawn(move || {
157            tx.send(f()).ok();
158        });
159        async { rx.await.map_err(|_| io::Error::other("spawn_blocking task cancelled")) }
160    }
161
162    /// Try to poll a value from the channel within the given deadline.
163    ///
164    /// By default, this first checks synchronously if a value is already available.
165    /// If not, it uses the AsyncRuntime timeout mechanism to wait until:
166    /// - a new element arrives
167    /// - the deadline is reached
168    #[since(version = "0.10.0")]
169    fn mpsc_recv_deadline<T: OptionalSend>(
170        receiver: &mut <Self::Mpsc as Mpsc>::Receiver<T>,
171        deadline: Self::Instant,
172    ) -> impl Future<Output = Result<T, TryRecvError>> + OptionalSend {
173        async move {
174            match receiver.try_recv() {
175                Ok(value) => Ok(value),
176                Err(TryRecvError::Disconnected) => Err(TryRecvError::Disconnected),
177                Err(TryRecvError::Empty) if <Self::Instant as Instant>::now() >= deadline => Err(TryRecvError::Empty),
178                Err(TryRecvError::Empty) => match Self::timeout_at(deadline, receiver.recv()).await {
179                    Ok(None) => Err(TryRecvError::Disconnected),
180                    Ok(Some(value)) => Ok(value),
181                    Err(_) => Err(TryRecvError::Empty),
182                },
183            }
184        }
185    }
186}