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