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 runtime;
18pub mod persistence;
19pub mod hierarchy;
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, 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) -> anyhow::Result<()> { 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    /// Register an event listener to observe lifecycle events.
113    ///
114    /// Event listeners receive notifications for task starts, failures, restarts, etc.
115    pub fn add_listener(mut self, listener: Arc<dyn SupervisorEventListener>) -> Self {
116        self.runtime.add_listener(listener);
117        self
118    }
119
120    /// Set a custom state store for persisting task states across restarts.
121    ///
122    /// The supervisor will use this store to load previous attempt counts and failure metadata
123    /// before starting tasks, and will update it as tasks run.
124    pub fn with_state_store(mut self, store: Arc<dyn TaskStateStore>) -> Self {
125        self.runtime.with_state_store(store);
126        self
127    }
128
129    /// Require a named async gate to resolve before any supervised task starts.
130    ///
131    /// Prerequisites run sequentially in the order they were registered.
132    /// If any prerequisite fails, the supervisor aborts startup.
133    pub fn require<F>(mut self, name: &'static str, fut: F) -> Self
134    where
135        F: Future<Output = anyhow::Result<()>> + Send + 'static,
136    {
137        self.runtime.add_prerequisite(name, Box::pin(fut));
138        self
139    }
140
141    /// Require a named async gate using a closure.
142    pub fn require_fn<F, Fut>(mut self, name: &'static str, f: F) -> Self
143    where
144        F: FnOnce() -> Fut + Send + 'static,
145        Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
146    {
147        self.runtime.add_prerequisite_fn(name, f);
148        self
149    }
150
151    /// Consume the builder and return the underlying [`TaskRuntime`].
152    ///
153    /// Note: This does not start the tasks. You must call `start_all()` on the returned runtime.
154    pub fn runtime(self) -> crate::runtime::TaskRuntime {
155        self.runtime
156    }
157
158    /// Start all registered tasks and return the [`TaskRuntime`] handle.
159    ///
160    /// This validates the dependency graph and executes prerequisites before spawning tasks.
161    ///
162    /// # Errors
163    /// Returns [`SupervisorError`] if prerequisites fail or if the dependency graph is invalid.
164    pub async fn start(
165        mut self,
166    ) -> Result<crate::runtime::TaskRuntime, crate::error::SupervisorError> {
167        self.runtime.start_all().await?;
168        Ok(self.runtime)
169    }
170
171    /// Start all tasks and block until the first one terminates.
172    ///
173    /// # Errors
174    /// Returns [`SupervisorError`] if startup validation or prerequisites fail.
175    pub async fn start_and_wait_any(
176        mut self,
177    ) -> Result<SupervisionResult, crate::error::SupervisorError> {
178        self.runtime.start_all().await?;
179        Ok(self.runtime.wait_any().await)
180    }
181
182    /// Start all tasks and block until all have terminated.
183    ///
184    /// # Errors
185    /// Returns [`SupervisorError`] if startup validation or prerequisites fail.
186    pub async fn start_and_wait_all(
187        mut self,
188    ) -> Result<Vec<SupervisionResult>, crate::error::SupervisorError> {
189        self.runtime.start_all().await?;
190        Ok(self.runtime.wait_all().await)
191    }
192}
193
194impl Default for Supervisor {
195    /// Create a new supervisor with default settings.
196    fn default() -> Self {
197        Self::new()
198    }
199}