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}