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}