zrx_executor/executor/strategy/immediate.rs
1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Immediate execution strategy.
27
28use std::fmt::{self, Debug};
29
30use crate::executor::strategy::Strategy;
31use crate::executor::task::Task;
32use crate::executor::Result;
33
34// ----------------------------------------------------------------------------
35// Structs
36// ----------------------------------------------------------------------------
37
38/// Immediate execution strategy.
39///
40/// This strategy executes a given task immediately, and is primarily intended
41/// for testing and debugging purposes. No threading is involved, which makes
42/// it much easier to reason about execution order. Additionally, it can act
43/// as a baseline for comparison with worker-based strategies.
44///
45/// __Warning__: When channels are involved, this strategy might deadlock, i.e.,
46/// when a task from a worker thread tries to send on a bounded channel that is
47/// full. In this case, make sure to either use unbounded channels, or actively
48/// drain the channel before sending on it.
49///
50/// # Examples
51///
52/// ```
53/// # use std::error::Error;
54/// # fn main() -> Result<(), Box<dyn Error>> {
55/// use zrx_executor::strategy::{Immediate, Strategy};
56///
57/// // Create strategy and submit task
58/// let strategy = Immediate::default();
59/// strategy.submit(Box::new(|| println!("Task")))?;
60/// # Ok(())
61/// # }
62/// ```
63pub struct Immediate;
64
65// ----------------------------------------------------------------------------
66// Implementations
67// ----------------------------------------------------------------------------
68
69impl Immediate {
70 /// Creates an immediate execution strategy.
71 ///
72 /// # Examples
73 ///
74 /// ```
75 /// use zrx_executor::strategy::Immediate;
76 ///
77 /// // Create strategy
78 /// let strategy = Immediate::new();
79 /// ```
80 #[must_use]
81 pub fn new() -> Self {
82 Self
83 }
84}
85
86// ----------------------------------------------------------------------------
87// Trait implementations
88// ----------------------------------------------------------------------------
89
90impl Strategy for Immediate {
91 /// Submits a task.
92 ///
93 /// This method immediately executes the given [`Task`], so that it runs on
94 /// the main thread, which is primarily intended for testing and debugging.
95 ///
96 /// Note that tasks are intended to only run once, which is why they are
97 /// consumed. If a task needs to be run multiple times, it must be wrapped
98 /// in a closure that creates a new task each time. This allows for safe
99 /// sharing of state between tasks.
100 ///
101 /// # Errors
102 ///
103 /// This method is infallible, and will always return [`Ok`].
104 ///
105 /// Errors
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// # use std::error::Error;
111 /// # fn main() -> Result<(), Box<dyn Error>> {
112 /// use zrx_executor::strategy::{Immediate, Strategy};
113 ///
114 /// // Create strategy and submit task
115 /// let strategy = Immediate::default();
116 /// strategy.submit(Box::new(|| println!("Task")))?;
117 /// # Ok(())
118 /// # }
119 /// ```
120 fn submit(&self, task: Box<dyn Task>) -> Result {
121 let subtasks = task.execute();
122 if !subtasks.is_empty() {
123 // Subtasks are executed recursively, so in case a subtask produces
124 // further subtasks, they are executed in depth-first order
125 subtasks.execute();
126 }
127
128 // No errors occurred
129 Ok(())
130 }
131
132 /// Returns the number of workers.
133 ///
134 /// The number of usable workers is always `1` for this strategy, as tasks
135 /// are immediately executed on the main thread when submitted.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use zrx_executor::strategy::{Immediate, Strategy};
141 ///
142 /// // Get number of workers
143 /// let strategy = Immediate::new();
144 /// assert_eq!(strategy.num_workers(), 1);
145 /// ```
146 #[inline]
147 fn num_workers(&self) -> usize {
148 1
149 }
150
151 /// Returns the number of running tasks.
152 ///
153 /// The number of running tasks is always `0` for this strategy, as tasks
154 /// are immediately executed on the main thread when submitted.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use zrx_executor::strategy::{Immediate, Strategy};
160 ///
161 /// // Get number of running tasks
162 /// let strategy = Immediate::default();
163 /// assert_eq!(strategy.num_tasks_running(), 0);
164 /// ```
165 #[inline]
166 fn num_tasks_running(&self) -> usize {
167 0
168 }
169
170 /// Returns the number of pending tasks.
171 ///
172 /// The number of pending tasks is always `0` for this strategy, as tasks
173 /// are immediately executed on the main thread when submitted.
174 ///
175 /// # Examples
176 ///
177 /// ```
178 /// use zrx_executor::strategy::{Immediate, Strategy};
179 ///
180 /// // Get number of pending tasks
181 /// let strategy = Immediate::default();
182 /// assert_eq!(strategy.num_tasks_pending(), 0);
183 /// ```
184 #[inline]
185 fn num_tasks_pending(&self) -> usize {
186 0
187 }
188
189 /// Returns the capacity.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use zrx_executor::strategy::{Immediate, Strategy};
195 ///
196 /// // Get capacity
197 /// let strategy = Immediate::default();
198 /// assert_eq!(strategy.capacity(), 0);
199 /// ```
200 #[inline]
201 fn capacity(&self) -> usize {
202 0
203 }
204}
205
206// ----------------------------------------------------------------------------
207
208impl Default for Immediate {
209 /// Creates an immediate execution strategy.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// use zrx_executor::strategy::Immediate;
215 ///
216 /// // Create strategy
217 /// let strategy = Immediate::default();
218 /// ```
219 #[inline]
220 fn default() -> Self {
221 Self
222 }
223}
224
225// ----------------------------------------------------------------------------
226
227impl Debug for Immediate {
228 /// Formats the execution strategy for debugging.
229 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230 f.debug_struct("Immediate")
231 .field("workers", &self.num_workers())
232 .field("running", &self.num_tasks_running())
233 .field("pending", &self.num_tasks_pending())
234 .finish()
235 }
236}