Skip to main content

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}