1use serde::{Deserialize, Serialize};
2use std::time::Duration;
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub enum SupervisionStatus {
6 CompletedNormally,
8 MaxAttemptsReached,
10 ManuallyStopped,
12 RestartPrevented,
14 SetupFailed,
16 DependencyFailed,
18 CircuitBreakerOpened,
20}
21
22#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
24pub enum RestartPolicy {
25 #[default]
28 Always,
29
30 MaxAttempts(usize),
33
34 Never,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub enum TaskState {
41 Running,
43 Paused,
45 Retrying,
47 ShuttingDown,
49 Stopped,
51 CircuitBreakerOpen,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
57pub enum ControlMessage {
58 Pause,
60 Resume,
62 Stop,
64 Restart,
66 ResetCircuitBreaker,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub enum SupervisorEvent {
73 TaskRegistered { id: String, name: String },
75 TaskSetupStarted { id: String, name: String },
77 TaskSetupCompleted { id: String, name: String },
79 TaskSetupFailed {
81 id: String,
82 name: String,
83 error: String,
84 },
85 TaskStarted {
87 id: String,
88 name: String,
89 attempt: usize,
90 },
91 TaskFinished {
93 id: String,
94 name: String,
95 attempt: usize,
96 },
97 TaskFailed {
99 id: String,
100 name: String,
101 attempt: usize,
102 error: String,
103 },
104 TaskPanicked {
106 id: String,
107 name: String,
108 attempt: usize,
109 panic_info: String,
110 },
111 TaskBackoff {
113 id: String,
114 name: String,
115 attempt: usize,
116 delay: Duration,
117 },
118 TaskStopped { id: String, name: String },
120 TaskPaused { id: String, name: String },
122 TaskResumed { id: String, name: String },
124 TaskMaxAttemptsReached {
126 id: String,
127 name: String,
128 attempts: usize,
129 },
130 TaskRestartPrevented {
132 id: String,
133 name: String,
134 attempt: usize,
135 },
136 TaskRemoved { id: String, name: String },
138 CircuitBreakerTripped {
140 id: String,
141 name: String,
142 consecutive_failures: usize,
143 },
144 CircuitBreakerReset { id: String, name: String },
146 CircuitBreakerHalfOpen { id: String, name: String },
148 SupervisorShutdownStarted,
150 SupervisorShutdownCompleted,
152 TaskConfigUpdated {
154 id: String,
155 name: String,
156 field: String,
157 old_value: String,
158 new_value: String,
159 },
160}
161
162#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
164pub enum HealthStatus {
165 Healthy,
167
168 Degraded { reason: String },
170
171 Unhealthy { reason: String },
173
174 #[default]
176 Unknown,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct CircuitBreakerConfig {
182 pub failure_threshold: usize,
184 pub reset_timeout: Duration,
186}
187
188impl Default for CircuitBreakerConfig {
189 fn default() -> Self {
190 Self {
191 failure_threshold: 5,
192 reset_timeout: Duration::from_secs(60),
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct TaskConfig {
200 pub restart_policy: RestartPolicy,
202 pub backoff_strategy: BackoffStrategy,
204 pub enabled: bool,
206}
207
208impl TaskConfig {
209 pub fn from_task(task: &dyn crate::contracts::SupervisedTask) -> Self {
211 Self {
212 restart_policy: task.restart_policy(),
213 backoff_strategy: task.backoff_strategy(),
214 enabled: true,
215 }
216 }
217
218 pub fn new() -> Self {
220 Self {
221 restart_policy: RestartPolicy::default(),
222 backoff_strategy: BackoffStrategy::default(),
223 enabled: true,
224 }
225 }
226}
227
228impl Default for TaskConfig {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234pub enum BackoffStrategy {
236 Fixed(Duration),
240
241 Exponential { initial: Duration, max: Duration },
247
248 Linear {
254 initial: Duration,
255 increment: Duration,
256 max: Duration,
257 },
258
259 Fibonacci { initial: Duration, max: Duration },
265
266 Custom(std::sync::Arc<dyn Fn(usize) -> Duration + Send + Sync>),
271}
272
273impl std::fmt::Debug for BackoffStrategy {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 match self {
277 Self::Fixed(duration) => f.debug_tuple("Fixed").field(duration).finish(),
278 Self::Exponential { initial, max } => f
279 .debug_struct("Exponential")
280 .field("initial", initial)
281 .field("max", max)
282 .finish(),
283 Self::Linear {
284 initial,
285 increment,
286 max,
287 } => f
288 .debug_struct("Linear")
289 .field("initial", initial)
290 .field("increment", increment)
291 .field("max", max)
292 .finish(),
293 Self::Fibonacci { initial, max } => f
294 .debug_struct("Fibonacci")
295 .field("initial", initial)
296 .field("max", max)
297 .finish(),
298 Self::Custom(_) => f.debug_tuple("Custom").field(&"<function>").finish(),
299 }
300 }
301}
302
303impl Clone for BackoffStrategy {
305 fn clone(&self) -> Self {
306 match self {
307 Self::Fixed(duration) => Self::Fixed(*duration),
308 Self::Exponential { initial, max } => Self::Exponential {
309 initial: *initial,
310 max: *max,
311 },
312 Self::Linear {
313 initial,
314 increment,
315 max,
316 } => Self::Linear {
317 initial: *initial,
318 increment: *increment,
319 max: *max,
320 },
321 Self::Fibonacci { initial, max } => Self::Fibonacci {
322 initial: *initial,
323 max: *max,
324 },
325 Self::Custom(func) => {
326 Self::Custom(func.clone())
328 }
329 }
330 }
331}
332
333impl Default for BackoffStrategy {
334 fn default() -> Self {
335 Self::Exponential {
336 initial: Duration::from_secs(2),
337 max: Duration::from_secs(60),
338 }
339 }
340}
341
342impl BackoffStrategy {
343 pub fn calculate_delay(&self, attempt: usize) -> Duration {
345 match self {
346 Self::Fixed(duration) => *duration,
347
348 Self::Exponential { initial, max } => {
349 let exponent = attempt.saturating_sub(1).min(20);
351 let multiplier = 2_u32.saturating_pow(exponent as u32);
352 initial.saturating_mul(multiplier).min(*max)
353 }
354
355 Self::Linear {
356 initial,
357 increment,
358 max,
359 } => {
360 let total_increment = increment.saturating_mul(attempt.saturating_sub(1) as u32);
361 initial.saturating_add(total_increment).min(*max)
362 }
363
364 Self::Fibonacci { initial, max } => {
365 let fib_multiplier = Self::fibonacci(attempt);
366 initial.saturating_mul(fib_multiplier as u32).min(*max)
367 }
368
369 Self::Custom(func) => func(attempt),
370 }
371 }
372
373 fn fibonacci(n: usize) -> usize {
375 match n {
376 0 => 0,
377 1 | 2 => 1,
378 n => {
379 let mut a: usize = 1;
380 let mut b = 1;
381 for _ in 2..n {
382 let next = a.saturating_add(b);
383 a = b;
384 b = next;
385 }
386 b
387 }
388 }
389 }
390
391 pub fn fixed(duration: Duration) -> Self {
393 Self::Fixed(duration)
394 }
395
396 pub fn exponential() -> Self {
398 Self::Exponential {
399 initial: Duration::from_secs(2),
400 max: Duration::from_secs(60),
401 }
402 }
403
404 pub fn exponential_custom(initial: Duration, max: Duration) -> Self {
406 Self::Exponential { initial, max }
407 }
408
409 pub fn linear() -> Self {
411 Self::Linear {
412 initial: Duration::from_secs(5),
413 increment: Duration::from_secs(5),
414 max: Duration::from_secs(60),
415 }
416 }
417
418 pub fn linear_custom(initial: Duration, increment: Duration, max: Duration) -> Self {
420 Self::Linear {
421 initial,
422 increment,
423 max,
424 }
425 }
426
427 pub fn fibonacci_with_default() -> Self {
429 Self::Fibonacci {
430 initial: Duration::from_secs(1),
431 max: Duration::from_secs(60),
432 }
433 }
434
435 pub fn custom<F>(func: F) -> Self
437 where
438 F: Fn(usize) -> Duration + Send + Sync + 'static,
439 {
440 Self::Custom(std::sync::Arc::new(func))
441 }
442}
443
444impl HealthStatus {
445 pub fn is_healthy(&self) -> bool {
447 matches!(self, Self::Healthy)
448 }
449
450 pub fn is_unhealthy(&self) -> bool {
452 matches!(self, Self::Unhealthy { .. })
453 }
454
455 pub fn is_degraded(&self) -> bool {
457 matches!(self, Self::Degraded { .. })
458 }
459
460 pub fn reason(&self) -> Option<&str> {
462 match self {
463 Self::Degraded { reason } | Self::Unhealthy { reason } => Some(reason),
464 _ => None,
465 }
466 }
467
468 pub fn healthy() -> Self {
470 Self::Healthy
471 }
472
473 pub fn degraded(reason: impl Into<String>) -> Self {
475 Self::Degraded {
476 reason: reason.into(),
477 }
478 }
479
480 pub fn unhealthy(reason: impl Into<String>) -> Self {
482 Self::Unhealthy {
483 reason: reason.into(),
484 }
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_exponential_backoff() {
494 let strategy = BackoffStrategy::exponential();
495
496 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(2));
497 assert_eq!(strategy.calculate_delay(2), Duration::from_secs(4));
498 assert_eq!(strategy.calculate_delay(3), Duration::from_secs(8));
499 assert_eq!(strategy.calculate_delay(4), Duration::from_secs(16));
500 assert_eq!(strategy.calculate_delay(5), Duration::from_secs(32));
501 assert_eq!(strategy.calculate_delay(6), Duration::from_secs(60)); }
503
504 #[test]
505 fn test_linear_backoff() {
506 let strategy = BackoffStrategy::linear();
507
508 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(5));
509 assert_eq!(strategy.calculate_delay(2), Duration::from_secs(10));
510 assert_eq!(strategy.calculate_delay(3), Duration::from_secs(15));
511 assert_eq!(strategy.calculate_delay(4), Duration::from_secs(20));
512 }
513
514 #[test]
515 fn test_fibonacci_backoff() {
516 let strategy = BackoffStrategy::fibonacci_with_default();
517
518 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(1));
519 assert_eq!(strategy.calculate_delay(2), Duration::from_secs(1));
520 assert_eq!(strategy.calculate_delay(3), Duration::from_secs(2));
521 assert_eq!(strategy.calculate_delay(4), Duration::from_secs(3));
522 assert_eq!(strategy.calculate_delay(5), Duration::from_secs(5));
523 assert_eq!(strategy.calculate_delay(6), Duration::from_secs(8));
524 }
525
526 #[test]
527 fn test_fixed_backoff() {
528 let strategy = BackoffStrategy::fixed(Duration::from_secs(10));
529
530 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(10));
531 assert_eq!(strategy.calculate_delay(5), Duration::from_secs(10));
532 assert_eq!(strategy.calculate_delay(100), Duration::from_secs(10));
533 }
534
535 #[test]
536 fn test_custom_backoff() {
537 let strategy = BackoffStrategy::custom(|attempt| Duration::from_secs(attempt as u64 * 3));
538
539 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(3));
540 assert_eq!(strategy.calculate_delay(2), Duration::from_secs(6));
541 assert_eq!(strategy.calculate_delay(5), Duration::from_secs(15));
542 }
543
544 #[test]
545 fn test_health_status() {
546 let healthy = HealthStatus::healthy();
547 assert!(healthy.is_healthy());
548 assert!(!healthy.is_unhealthy());
549 assert_eq!(healthy.reason(), None);
550
551 let degraded = HealthStatus::degraded("High memory usage");
552 assert!(degraded.is_degraded());
553 assert_eq!(degraded.reason(), Some("High memory usage"));
554
555 let unhealthy = HealthStatus::unhealthy("Connection lost");
556 assert!(unhealthy.is_unhealthy());
557 assert_eq!(unhealthy.reason(), Some("Connection lost"));
558 }
559
560 #[test]
561 fn test_backoff_debug() {
562 let exponential = BackoffStrategy::exponential();
563 let debug_str = format!("{:?}", exponential);
564 assert!(debug_str.contains("Exponential"));
565
566 let custom = BackoffStrategy::custom(|n| Duration::from_secs(n as u64));
567 let debug_str = format!("{:?}", custom);
568 assert!(debug_str.contains("Custom"));
569 }
570
571 #[test]
572 fn test_backoff_clone() {
573 let original = BackoffStrategy::exponential();
574 let cloned = original.clone();
575
576 assert_eq!(original.calculate_delay(3), cloned.calculate_delay(3));
577 }
578}