Skip to main content

foxtive_supervisor/
contracts.rs

1use crate::enums::{
2    BackoffStrategy, CircuitBreakerConfig, HealthStatus, RestartPolicy, SupervisorEvent, TaskState,
3};
4use crate::error::SupervisorResult;
5use std::time::Duration;
6
7/// Core trait for any long-running task that needs supervision
8#[async_trait::async_trait]
9pub trait SupervisedTask: Send + Sync {
10    // REQUIRED METHODS
11
12    /// Unique identifier for this task (used in logs, monitoring, and dependency resolution)
13    fn id(&self) -> &'static str;
14
15    /// Human-readable name for this task (used in logs and monitoring)
16    fn name(&self) -> String {
17        self.id().to_string()
18    }
19
20    /// Main task execution - this should run until completion or error
21    async fn run(&self) -> SupervisorResult<()>;
22
23    // DEPENDENCY MANAGEMENT
24
25    /// Declare task IDs that must complete setup before this task starts
26    fn dependencies(&self) -> &'static [&'static str] {
27        &[]
28    }
29
30    // OPTIONAL CONFIGURATION
31
32    /// Restart policy when task fails or panics
33    fn restart_policy(&self) -> RestartPolicy {
34        RestartPolicy::Always
35    }
36
37    /// Backoff strategy between restart attempts
38    fn backoff_strategy(&self) -> BackoffStrategy {
39        BackoffStrategy::default()
40    }
41
42    /// Priority of the task (higher values = higher priority)
43    /// Used when concurrency limits are reached to decide which tasks start first
44    fn priority(&self) -> i32 {
45        0
46    }
47
48    /// Optional concurrency limit for this specific task
49    /// This is useful if the task is part of a pool or if multiple instances exist
50    fn concurrency_limit(&self) -> Option<usize> {
51        None
52    }
53
54    /// Configuration for the task's circuit breaker
55    /// If `None`, the circuit breaker is disabled for this task
56    fn circuit_breaker(&self) -> Option<CircuitBreakerConfig> {
57        None
58    }
59
60    /// Maximum time to wait for the task to finish its `run()` and `on_shutdown()`
61    /// before forced termination during supervisor shutdown.
62    fn shutdown_timeout(&self) -> Duration {
63        Duration::from_secs(30)
64    }
65
66    /// Optional cron expression for scheduled execution
67    #[cfg(feature = "cron")]
68    fn cron_schedule(&self) -> Option<&'static str> {
69        None
70    }
71
72    /// Optional initial delay before the first execution
73    ///
74    /// This is useful for staggering task startup to prevent thundering herd problems
75    /// or for giving dependencies time to initialize.
76    fn initial_delay(&self) -> Option<Duration> {
77        None
78    }
79
80    /// Optional jitter to add randomness to the initial delay
81    ///
82    /// Returns a tuple of (min_jitter, max_jitter) that will be randomly added
83    /// to the initial delay. This helps prevent thundering herd problems in
84    /// distributed deployments where multiple instances start simultaneously.
85    ///
86    /// Example: If initial_delay is 5s and jitter is (0, 2s), the actual delay
87    /// will be between 5s and 7s.
88    fn jitter(&self) -> Option<(Duration, Duration)> {
89        None
90    }
91
92    /// Minimum time between restart attempts (rate limiting)
93    ///
94    /// This prevents tasks from restarting too frequently, which can overwhelm
95    /// external services or resources. If a task fails and wants to restart,
96    /// it will wait at least this duration before attempting again.
97    ///
98    /// This is applied in addition to the backoff strategy - the actual delay
99    /// will be the maximum of the backoff delay and this minimum restart interval.
100    fn min_restart_interval(&self) -> Option<Duration> {
101        None
102    }
103
104    /// Time window restrictions for task execution
105    ///
106    /// Returns optional start and end times (as hours in 24-hour format) during
107    /// which the task is allowed to run. Outside this window, the task will wait
108    /// until the next allowed time.
109    ///
110    /// Example: Some((9, 17)) means the task can only run between 9 AM and 5 PM.
111    /// Use None for either value to indicate no restriction on that boundary.
112    ///
113    /// Note: Times are evaluated in UTC by default.
114    fn execution_time_window(&self) -> Option<(Option<u8>, Option<u8>)> {
115        None
116    }
117
118    /// Task group identifier for atomic operations
119    ///
120    /// Tasks with the same group ID can be started, stopped, or managed together.
121    /// This is useful for related tasks that should be treated as a unit.
122    ///
123    /// Example: A database connection pool and its query processor might be in
124    /// the same group so they start and stop together.
125    fn group_id(&self) -> Option<&'static str> {
126        None
127    }
128
129    /// Conditional dependencies based on environment or runtime conditions
130    ///
131    /// Returns a list of dependencies that are only active when certain conditions are met.
132    /// Each tuple contains (dependency_id, condition_function).
133    /// The condition function receives the current environment context and returns true
134    /// if the dependency should be enforced.
135    ///
136    /// Example: Only depend on "cache-service" if USE_CACHE env var is set
137    /// ```ignore
138    /// vec![("cache-service", Box::new(|| std::env::var("USE_CACHE").is_ok()))]
139    /// ```
140    #[allow(clippy::type_complexity)]
141    fn conditional_dependencies(&self) -> Vec<(&'static str, Box<dyn Fn() -> bool + Send + Sync>)> {
142        Vec::new()
143    }
144
145    /// Get all active dependencies (regular + conditional that evaluate to true)
146    ///
147    /// This combines regular dependencies with conditional dependencies whose
148    /// conditions currently evaluate to true.
149    fn active_dependencies(&self) -> Vec<&'static str> {
150        let mut deps = self.dependencies().to_vec();
151
152        // Add conditional dependencies whose conditions are met
153        for (dep_id, condition) in self.conditional_dependencies() {
154            if condition() {
155                deps.push(dep_id);
156            }
157        }
158
159        deps
160    }
161
162    // LIFECYCLE HOOKS
163
164    /// Called once before the first run() attempt
165    async fn setup(&self) -> SupervisorResult<()> {
166        Ok(())
167    }
168
169    /// Called after task stops (success, failure, or panic)
170    ///
171    /// **Purpose:** Internal resource cleanup and teardown.
172    ///
173    /// This hook is called automatically by the supervision loop whenever a task's
174    /// `run()` method completes (successfully or with error) or panics. It's meant for:
175    /// - Closing database connections
176    /// - Releasing file handles
177    /// - Dropping temporary resources
178    /// - Any internal cleanup needed before restart or shutdown
179    ///
180    /// **When it's called:**
181    /// - After every `run()` completion (success or failure)
182    /// - After a panic is caught
183    /// - When task receives Stop command
184    /// - During supervisor shutdown
185    ///
186    /// **Important:** This is NOT for graceful shutdown logic. Use `on_shutdown()` instead
187    /// if you need to perform actions specifically during supervisor-initiated shutdown.
188    async fn cleanup(&self) {
189        // Default: no cleanup
190    }
191
192    // RESTART CONTROL
193
194    /// Dynamic restart control - called before each restart
195    async fn should_restart(&self, _attempt: usize, _last_error: &str) -> bool {
196        true
197    }
198
199    // MONITORING & OBSERVABILITY
200
201    /// Health check for monitoring endpoints
202    async fn health_check(&self) -> HealthStatus {
203        HealthStatus::Healthy
204    }
205
206    /// Return task-specific metrics (optional)
207    async fn metrics(&self) -> Option<TaskMetrics> {
208        None
209    }
210
211    /// Called before each restart (not called on first attempt)
212    async fn on_restart(&self, _attempt: usize) {
213        // Default: no action
214    }
215
216    // ERROR HANDLING HOOKS
217
218    /// Called when run() returns an error
219    async fn on_error(&self, _error: &str, _attempt: usize) {
220        // Default: no action (supervisor logs by default)
221    }
222
223    /// Called when run() panics
224    async fn on_panic(&self, _panic_info: &str, _attempt: usize) {
225        // Default: no action (supervisor logs by default)
226    }
227
228    /// Called when the service is shutting down
229    ///
230    /// **Purpose:** Graceful shutdown hook for external/user-defined cleanup.
231    ///
232    /// This hook is called ONLY during supervisor-initiated graceful shutdown,
233    /// AFTER the supervision loop has terminated. It's meant for:
234    /// - Flushing buffers to disk/network
235    /// - Sending final notifications
236    /// - Performing graceful disconnections
237    /// - Any user-defined shutdown logic that should run once at the end
238    ///
239    /// **When it's called:**
240    /// - ONLY during `TaskRuntime::shutdown()`
241    /// - AFTER the supervision loop ends
242    /// - AFTER `cleanup()` has been called
243    /// - Once per task, in dependency-aware order
244    ///
245    /// **Key difference from `cleanup()`:**
246    /// - `cleanup()` = Internal teardown, called after EVERY run() completion
247    /// - `on_shutdown()` = Graceful shutdown, called ONCE during supervisor shutdown
248    ///
249    /// **Example:**
250    /// ```ignore
251    /// async fn on_shutdown(&self) {
252    ///     // Flush pending messages
253    ///     self.message_queue.flush().await.ok();
254    ///     // Notify monitoring service
255    ///     self.notify_shutdown().await.ok();
256    /// }
257    /// ```
258    async fn on_shutdown(&self) {
259        // Default implementation does nothing
260    }
261}
262
263/// Extension trait for tasks that need state tracking
264#[async_trait::async_trait]
265pub trait StatefulTask: SupervisedTask {
266    /// Get the current state of the task
267    async fn state(&self) -> TaskState;
268}
269
270/// Trait for components that listen to supervisor events
271#[async_trait::async_trait]
272pub trait SupervisorEventListener: Send + Sync {
273    /// Called when a supervisor event occurs
274    async fn on_event(&self, event: SupervisorEvent);
275}
276
277/// Optional metrics that tasks can expose
278#[derive(Debug, Clone)]
279pub struct TaskMetrics {
280    /// Number of successful operations
281    pub success_count: u64,
282    /// Number of failed operations
283    pub error_count: u64,
284    /// Number of messages/items processed
285    pub processed_count: u64,
286    /// Current processing rate (per second)
287    pub processing_rate: f64,
288    /// Average processing time (milliseconds)
289    pub avg_processing_time_ms: f64,
290    /// Custom metrics (key-value pairs)
291    pub custom: std::collections::HashMap<String, f64>,
292}
293
294impl Default for TaskMetrics {
295    fn default() -> Self {
296        Self {
297            success_count: 0,
298            error_count: 0,
299            processed_count: 0,
300            processing_rate: 0.0,
301            avg_processing_time_ms: 0.0,
302            custom: std::collections::HashMap::new(),
303        }
304    }
305}
306
307impl TaskMetrics {
308    pub fn new() -> Self {
309        Self::default()
310    }
311
312    /// Add a custom metric
313    pub fn add_custom(&mut self, key: impl Into<String>, value: f64) {
314        self.custom.insert(key.into(), value);
315    }
316}