Skip to main content

tower_resilience_executor/
executor.rs

1//! Executor trait for spawning futures.
2
3use std::future::Future;
4use tokio::task::JoinHandle;
5
6/// Trait for executors that can spawn futures.
7///
8/// This trait abstracts over different execution strategies, allowing
9/// services to be run on dedicated runtimes, thread pools, or with
10/// different spawning strategies.
11///
12/// # Example
13///
14/// ```rust,no_run
15/// use tower_resilience_executor::Executor;
16/// use tokio::runtime::Handle;
17///
18/// // Tokio Handle implements Executor
19/// let handle = Handle::current();
20/// ```
21pub trait Executor: Clone + Send + Sync + 'static {
22    /// Spawns a future onto this executor.
23    ///
24    /// Returns a handle that can be used to await the result.
25    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
26    where
27        F: Future + Send + 'static,
28        F::Output: Send + 'static;
29}
30
31/// Executor implementation for tokio's runtime Handle.
32///
33/// This spawns futures as new tasks on the tokio runtime.
34impl Executor for tokio::runtime::Handle {
35    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
36    where
37        F: Future + Send + 'static,
38        F::Output: Send + 'static,
39    {
40        tokio::runtime::Handle::spawn(self, future)
41    }
42}
43
44/// An executor that uses `spawn_blocking` for blocking operations.
45///
46/// This is useful for services that perform blocking I/O or CPU-intensive
47/// work that would block the async runtime.
48///
49/// # Example
50///
51/// ```rust,no_run
52/// use tower_resilience_executor::BlockingExecutor;
53/// use tokio::runtime::Handle;
54///
55/// let executor = BlockingExecutor::new(Handle::current());
56/// ```
57#[derive(Clone)]
58pub struct BlockingExecutor {
59    handle: tokio::runtime::Handle,
60}
61
62impl BlockingExecutor {
63    /// Creates a new blocking executor using the given runtime handle.
64    pub fn new(handle: tokio::runtime::Handle) -> Self {
65        Self { handle }
66    }
67
68    /// Creates a new blocking executor using the current runtime handle.
69    ///
70    /// # Panics
71    ///
72    /// Panics if called from outside a tokio runtime.
73    pub fn current() -> Self {
74        Self::new(tokio::runtime::Handle::current())
75    }
76}
77
78impl Executor for BlockingExecutor {
79    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
80    where
81        F: Future + Send + 'static,
82        F::Output: Send + 'static,
83    {
84        // We need to spawn the future on the runtime, then use spawn_blocking
85        // for the actual work. However, spawn_blocking is for sync code.
86        // For async code that may block, we spawn normally but on the dedicated handle.
87        self.handle.spawn(future)
88    }
89}
90
91/// An executor wrapper that spawns on the current runtime.
92///
93/// This is a convenience type that captures the current runtime handle
94/// at construction time.
95#[derive(Clone)]
96pub struct CurrentRuntime {
97    handle: tokio::runtime::Handle,
98}
99
100impl CurrentRuntime {
101    /// Creates a new executor using the current runtime handle.
102    ///
103    /// # Panics
104    ///
105    /// Panics if called from outside a tokio runtime.
106    pub fn new() -> Self {
107        Self {
108            handle: tokio::runtime::Handle::current(),
109        }
110    }
111}
112
113impl Default for CurrentRuntime {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl Executor for CurrentRuntime {
120    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
121    where
122        F: Future + Send + 'static,
123        F::Output: Send + 'static,
124    {
125        self.handle.spawn(future)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[tokio::test]
134    async fn test_handle_executor() {
135        let handle = tokio::runtime::Handle::current();
136        let join = handle.spawn(async { 42 });
137        assert_eq!(join.await.unwrap(), 42);
138    }
139
140    #[tokio::test]
141    async fn test_current_runtime_executor() {
142        let executor = CurrentRuntime::new();
143        let join = executor.spawn(async { 42 });
144        assert_eq!(join.await.unwrap(), 42);
145    }
146
147    #[tokio::test]
148    async fn test_blocking_executor() {
149        let executor = BlockingExecutor::current();
150        let join = executor.spawn(async { 42 });
151        assert_eq!(join.await.unwrap(), 42);
152    }
153}