Skip to main content

foxtive_supervisor/
lib.rs

1//! # 🦊 Foxtive Supervisor
2//!
3//! A Rust supervision library that keeps your async tasks running, even when things go wrong.
4//! It provides automatic restarts, dependency management, dynamic task management, and deep observability.
5//!
6//! ## Core Components
7//!
8//! - [`Supervisor`]: The primary entry point for configuring and starting supervised tasks.
9//! - [`TaskRuntime`]: The underlying runtime handle for managing tasks after they've started.
10//! - [`SupervisedTask`]: The trait your tasks must implement to be supervised.
11//! - [`persistence::TaskStateStore`]: Interface for persisting task state across restarts.
12//! - [`SupervisorEventListener`]: Interface for listening to supervisor-wide events.
13
14pub mod contracts;
15pub mod enums;
16pub mod error;
17pub mod hierarchy;
18pub mod persistence;
19pub mod runtime;
20pub mod task_pool;
21
22#[cfg(feature = "distributed")]
23pub mod distributed;
24
25use std::future::Future;
26use std::sync::Arc;
27
28pub use crate::contracts::{SupervisedTask, SupervisorEventListener};
29pub use crate::enums::TaskConfig;
30pub use crate::error::{SupervisorError, SupervisorResult, ValidationError};
31pub use crate::persistence::TaskStateStore;
32pub use crate::runtime::{SupervisionResult, TaskRuntime, spawn_supervised, spawn_supervised_many};
33
34/// Builder for constructing and starting a supervisor.
35///
36/// `Supervisor` provides a fluent API for registering tasks, setting up prerequisites,
37/// and configuring global runtime options like concurrency limits and persistence.
38///
39/// # Example
40///
41/// ```rust
42/// use foxtive_supervisor::{Supervisor, SupervisedTask};
43///
44/// struct MyTask;
45/// #[async_trait::async_trait]
46/// impl SupervisedTask for MyTask {
47///     fn id(&self) -> &'static str { "my-task" }
48///     async fn run(&self) -> foxtive_supervisor::SupervisorResult<()> { Ok(()) }
49/// }
50///
51/// #[tokio::main]
52/// async fn main() {
53///     Supervisor::new()
54///         .add(MyTask)
55///         .start_and_wait_any()
56///         .await
57///         .unwrap();
58/// }
59/// ```
60pub struct Supervisor {
61    runtime: crate::runtime::TaskRuntime,
62}
63
64impl Supervisor {
65    /// Create a new, empty supervisor.
66    pub fn new() -> Self {
67        Self {
68            runtime: crate::runtime::TaskRuntime::new(),
69        }
70    }
71
72    /// Add a task to be supervised.
73    ///
74    /// The task must implement the [`SupervisedTask`] trait.
75    #[allow(clippy::should_implement_trait)]
76    pub fn add<T: SupervisedTask + 'static>(mut self, task: T) -> Self {
77        self.runtime.register(task);
78        self
79    }
80
81    /// Add multiple tasks of the same type.
82    pub fn add_many<T: SupervisedTask + 'static>(mut self, tasks: Vec<T>) -> Self {
83        self.runtime.register_many(tasks);
84        self
85    }
86
87    /// Add a task already wrapped in a `Box`.
88    ///
89    /// Useful when managing a heterogeneous collection of tasks.
90    pub fn add_boxed(mut self, task: Box<dyn SupervisedTask>) -> Self {
91        self.runtime.register_boxed(task);
92        self
93    }
94
95    /// Add a task already wrapped in an `Arc`.
96    ///
97    /// This is the most efficient way to add a task if you already have an `Arc` handle.
98    pub fn add_arc(mut self, task: Arc<dyn SupervisedTask>) -> Self {
99        self.runtime.register_arc(task);
100        self
101    }
102
103    /// Set a global concurrency limit for the supervisor.
104    ///
105    /// This limits how many tasks can be in their `run()` loop simultaneously.
106    /// It's useful for preventing resource exhaustion during massive task restarts.
107    pub fn with_global_concurrency_limit(mut self, limit: usize) -> Self {
108        self.runtime.with_global_concurrency_limit(limit);
109        self
110    }
111
112    /// Set the event broadcast channel capacity.
113    ///
114    /// The default is 1024. Increase this if you have many tasks emitting
115    /// events rapidly and observe lagged event warnings.
116    pub fn with_event_channel_capacity(mut self, capacity: usize) -> Self {
117        self.runtime.with_event_channel_capacity(capacity);
118        self
119    }
120
121    /// Attach an application container for DI access within supervised tasks.
122    ///
123    /// Requires the `foxtive-app` feature to be enabled.
124    #[cfg(feature = "foxtive-app")]
125    pub fn app(mut self, app: std::sync::Arc<foxtive::App>) -> Self {
126        self.runtime.with_app(app);
127        self
128    }
129
130    /// Register an event listener to observe lifecycle events.
131    ///
132    /// Event listeners receive notifications for task starts, failures, restarts, etc.
133    pub fn add_listener(mut self, listener: Arc<dyn SupervisorEventListener>) -> Self {
134        self.runtime.add_listener(listener);
135        self
136    }
137
138    /// Set a custom state store for persisting task states across restarts.
139    ///
140    /// The supervisor will use this store to load previous attempt counts and failure metadata
141    /// before starting tasks, and will update it as tasks run.
142    pub fn with_state_store(mut self, store: Arc<dyn TaskStateStore>) -> Self {
143        self.runtime.with_state_store(store);
144        self
145    }
146
147    /// Require a named async gate to resolve before any supervised task starts.
148    ///
149    /// Prerequisites run sequentially in the order they were registered.
150    /// If any prerequisite fails, the supervisor aborts startup.
151    pub fn require<F>(mut self, name: &'static str, fut: F) -> Self
152    where
153        F: Future<Output = SupervisorResult<()>> + Send + 'static,
154    {
155        self.runtime.add_prerequisite(name, Box::pin(fut));
156        self
157    }
158
159    /// Require a named async gate using a closure.
160    pub fn require_fn<F, Fut>(mut self, name: &'static str, f: F) -> Self
161    where
162        F: FnOnce() -> Fut + Send + 'static,
163        Fut: Future<Output = SupervisorResult<()>> + Send + 'static,
164    {
165        self.runtime.add_prerequisite_fn(name, f);
166        self
167    }
168
169    /// Consume the builder and return the underlying [`TaskRuntime`].
170    ///
171    /// Note: This does not start the tasks. You must call `start_all()` on the returned runtime.
172    pub fn runtime(self) -> crate::runtime::TaskRuntime {
173        self.runtime
174    }
175
176    /// Start all registered tasks and return the [`TaskRuntime`] handle.
177    ///
178    /// This validates the dependency graph and executes prerequisites before spawning tasks.
179    ///
180    /// # Errors
181    /// Returns [`SupervisorError`] if prerequisites fail or if the dependency graph is invalid.
182    pub async fn start(
183        mut self,
184    ) -> Result<crate::runtime::TaskRuntime, crate::error::SupervisorError> {
185        self.runtime.start_all().await?;
186        Ok(self.runtime)
187    }
188
189    /// Start all tasks and block until the first one terminates.
190    ///
191    /// # Errors
192    /// Returns [`SupervisorError`] if startup validation or prerequisites fail.
193    pub async fn start_and_wait_any(
194        mut self,
195    ) -> Result<SupervisionResult, crate::error::SupervisorError> {
196        self.runtime.start_all().await?;
197        Ok(self.runtime.wait_any().await)
198    }
199
200    /// Start all tasks and block until all have terminated.
201    ///
202    /// # Errors
203    /// Returns [`SupervisorError`] if startup validation or prerequisites fail.
204    pub async fn start_and_wait_all(
205        mut self,
206    ) -> Result<Vec<SupervisionResult>, crate::error::SupervisorError> {
207        self.runtime.start_all().await?;
208        Ok(self.runtime.wait_all().await)
209    }
210}
211
212impl Default for Supervisor {
213    /// Create a new supervisor with default settings.
214    fn default() -> Self {
215        Self::new()
216    }
217}