1pub mod checkpoint;
19pub mod checkpoint_recovery;
20pub mod exactly_once;
21pub use checkpoint::{
22 CheckpointController, CheckpointControllerConfig, CheckpointError, CheckpointId,
23 CheckpointProgress, CheckpointResult, CheckpointStore, InMemoryCheckpointStore, InputEdgeId,
24 Marker, MarkerPropagator, MarkerPropagatorEvent, OperatorId, OperatorSnapshot,
25};
26pub use checkpoint_recovery::*;
27pub use exactly_once::{
28 EndToEndExactlyOnceCoordinator, ExactlyOnceCoordinatorConfig, ExactlyOnceCoordinatorStats,
29 ExactlyOnceError, ExactlyOnceResult, ExactlyOnceStatsSnapshot, IdempotentProducer,
30 IdempotentProducerConfig, ProducerStamp,
31};
32
33use std::collections::HashMap;
34use std::sync::Arc;
35use std::time::{Duration, Instant, SystemTime};
36
37use parking_lot::RwLock;
38use serde::{Deserialize, Serialize};
39use thiserror::Error;
40use tracing::{debug, info, warn};
41
42#[derive(Error, Debug, Clone)]
46pub enum FaultToleranceError {
47 #[error("Bulkhead full: compartment {compartment} has reached capacity {capacity}")]
48 BulkheadFull {
49 compartment: String,
50 capacity: usize,
51 },
52
53 #[error("Max retries exceeded: {attempts} attempts for operation {operation}")]
54 MaxRetriesExceeded { attempts: u32, operation: String },
55
56 #[error("Worker {worker_id} failed to restart after {attempts} attempts")]
57 SupervisorRestartFailed { worker_id: String, attempts: u32 },
58
59 #[error("Health check failed: metric {metric} value {value} exceeds threshold {threshold}")]
60 HealthCheckFailed {
61 metric: String,
62 value: f64,
63 threshold: f64,
64 },
65
66 #[error("Operation timeout after {elapsed_ms}ms (limit {timeout_ms}ms)")]
67 OperationTimeout { elapsed_ms: u64, timeout_ms: u64 },
68}
69
70pub type FaultResult<T> = Result<T, FaultToleranceError>;
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct HealthThreshold {
78 pub metric_name: String,
80 pub warn_threshold: f64,
82 pub critical_threshold: f64,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub enum HealthAlertSeverity {
89 Warning,
91 Critical,
93 Recovered,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct HealthAlert {
100 pub metric_name: String,
102 pub current_value: f64,
104 pub threshold: f64,
106 pub severity: HealthAlertSeverity,
108 pub raised_at: SystemTime,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub enum StreamHealthStatus {
115 Healthy,
117 Degraded,
119 Critical,
121 Unknown,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct HealthSnapshot {
128 pub status: StreamHealthStatus,
130 pub metrics: HashMap<String, f64>,
132 pub active_alerts: Vec<HealthAlert>,
134 pub snapshot_time: SystemTime,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct HealthMonitorConfig {
141 pub thresholds: Vec<HealthThreshold>,
143 pub metric_staleness: Duration,
145 pub check_interval: Duration,
147}
148
149impl Default for HealthMonitorConfig {
150 fn default() -> Self {
151 Self {
152 thresholds: vec![
153 HealthThreshold {
154 metric_name: "error_rate".to_string(),
155 warn_threshold: 0.01,
156 critical_threshold: 0.05,
157 },
158 HealthThreshold {
159 metric_name: "latency_p99_ms".to_string(),
160 warn_threshold: 100.0,
161 critical_threshold: 500.0,
162 },
163 HealthThreshold {
164 metric_name: "backpressure_ratio".to_string(),
165 warn_threshold: 0.5,
166 critical_threshold: 0.9,
167 },
168 ],
169 metric_staleness: Duration::from_secs(60),
170 check_interval: Duration::from_secs(5),
171 }
172 }
173}
174
175pub struct StreamHealthMonitor {
180 config: HealthMonitorConfig,
181 metrics: Arc<RwLock<HashMap<String, (f64, Instant)>>>,
183 active_alerts: Arc<RwLock<Vec<HealthAlert>>>,
185 alert_history: Arc<RwLock<Vec<HealthAlert>>>,
187 total_alerts_raised: Arc<RwLock<u64>>,
189}
190
191impl StreamHealthMonitor {
192 pub fn new(config: HealthMonitorConfig) -> Self {
194 Self {
195 config,
196 metrics: Arc::new(RwLock::new(HashMap::new())),
197 active_alerts: Arc::new(RwLock::new(Vec::new())),
198 alert_history: Arc::new(RwLock::new(Vec::new())),
199 total_alerts_raised: Arc::new(RwLock::new(0)),
200 }
201 }
202
203 pub fn record_metric(&self, metric_name: &str, value: f64) -> Vec<HealthAlert> {
207 self.metrics
208 .write()
209 .insert(metric_name.to_string(), (value, Instant::now()));
210 self.evaluate_thresholds(metric_name, value)
211 }
212
213 pub fn snapshot(&self) -> HealthSnapshot {
215 let metrics = self.metrics.read();
216 let now = Instant::now();
217 let stale_limit = self.config.metric_staleness;
218
219 let all_fresh = metrics
221 .values()
222 .all(|(_, ts)| now.duration_since(*ts) < stale_limit);
223
224 let metric_values: HashMap<String, f64> =
225 metrics.iter().map(|(k, (v, _))| (k.clone(), *v)).collect();
226
227 let active_alerts = self.active_alerts.read().clone();
228
229 let status = if !all_fresh || metric_values.is_empty() {
230 StreamHealthStatus::Unknown
231 } else if active_alerts
232 .iter()
233 .any(|a| a.severity == HealthAlertSeverity::Critical)
234 {
235 StreamHealthStatus::Critical
236 } else if active_alerts
237 .iter()
238 .any(|a| a.severity == HealthAlertSeverity::Warning)
239 {
240 StreamHealthStatus::Degraded
241 } else {
242 StreamHealthStatus::Healthy
243 };
244
245 HealthSnapshot {
246 status,
247 metrics: metric_values,
248 active_alerts,
249 snapshot_time: SystemTime::now(),
250 }
251 }
252
253 pub fn current_metric(&self, name: &str) -> Option<f64> {
255 self.metrics.read().get(name).map(|(v, _)| *v)
256 }
257
258 pub fn total_alerts_raised(&self) -> u64 {
260 *self.total_alerts_raised.read()
261 }
262
263 fn evaluate_thresholds(&self, metric_name: &str, value: f64) -> Vec<HealthAlert> {
264 let mut new_alerts = Vec::new();
265 let thresholds = self.config.thresholds.clone();
266
267 for threshold in &thresholds {
268 if threshold.metric_name != metric_name {
269 continue;
270 }
271 let severity = if value >= threshold.critical_threshold {
272 Some(HealthAlertSeverity::Critical)
273 } else if value >= threshold.warn_threshold {
274 Some(HealthAlertSeverity::Warning)
275 } else {
276 let mut active = self.active_alerts.write();
278 active.retain(|a| a.metric_name != metric_name);
279 None
280 };
281
282 if let Some(sev) = severity {
283 let threshold_val = if sev == HealthAlertSeverity::Critical {
284 threshold.critical_threshold
285 } else {
286 threshold.warn_threshold
287 };
288 let alert = HealthAlert {
289 metric_name: metric_name.to_string(),
290 current_value: value,
291 threshold: threshold_val,
292 severity: sev,
293 raised_at: SystemTime::now(),
294 };
295 let mut active = self.active_alerts.write();
297 active.retain(|a| a.metric_name != metric_name);
298 active.push(alert.clone());
299 drop(active);
300
301 let mut history = self.alert_history.write();
303 if history.len() >= 1000 {
304 history.remove(0);
305 }
306 history.push(alert.clone());
307
308 *self.total_alerts_raised.write() += 1;
309 new_alerts.push(alert);
310 debug!("Health alert raised for metric {}: {}", metric_name, value);
311 }
312 }
313 new_alerts
314 }
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct CompartmentStats {
322 pub compartment_id: String,
324 pub capacity: usize,
326 pub active: usize,
328 pub rejected: u64,
330 pub accepted: u64,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct BulkheadConfig {
337 pub compartment_capacities: HashMap<String, usize>,
339 pub default_capacity: usize,
341}
342
343impl Default for BulkheadConfig {
344 fn default() -> Self {
345 let mut compartments = HashMap::new();
346 compartments.insert("critical".to_string(), 100);
347 compartments.insert("standard".to_string(), 50);
348 compartments.insert("background".to_string(), 20);
349 Self {
350 compartment_capacities: compartments,
351 default_capacity: 30,
352 }
353 }
354}
355
356pub struct BulkheadPermit {
358 compartment_id: String,
359 active_counter: Arc<RwLock<usize>>,
360}
361
362impl Drop for BulkheadPermit {
363 fn drop(&mut self) {
364 let mut active = self.active_counter.write();
365 if *active > 0 {
366 *active -= 1;
367 }
368 debug!(
369 "Bulkhead permit released for compartment {}",
370 self.compartment_id
371 );
372 }
373}
374
375struct Compartment {
377 capacity: usize,
378 active: Arc<RwLock<usize>>,
379 rejected: Arc<RwLock<u64>>,
380 accepted: Arc<RwLock<u64>>,
381}
382
383pub struct BulkheadIsolator {
388 compartments: Arc<RwLock<HashMap<String, Compartment>>>,
389 default_capacity: usize,
390}
391
392impl BulkheadIsolator {
393 pub fn new(config: BulkheadConfig) -> Self {
395 let mut compartments = HashMap::new();
396 for (id, capacity) in &config.compartment_capacities {
397 compartments.insert(
398 id.clone(),
399 Compartment {
400 capacity: *capacity,
401 active: Arc::new(RwLock::new(0)),
402 rejected: Arc::new(RwLock::new(0)),
403 accepted: Arc::new(RwLock::new(0)),
404 },
405 );
406 }
407 Self {
408 compartments: Arc::new(RwLock::new(compartments)),
409 default_capacity: config.default_capacity,
410 }
411 }
412
413 pub fn acquire(&self, compartment_id: &str) -> FaultResult<BulkheadPermit> {
417 let mut compartments = self.compartments.write();
418 let compartment = compartments
420 .entry(compartment_id.to_string())
421 .or_insert_with(|| Compartment {
422 capacity: self.default_capacity,
423 active: Arc::new(RwLock::new(0)),
424 rejected: Arc::new(RwLock::new(0)),
425 accepted: Arc::new(RwLock::new(0)),
426 });
427
428 let current = *compartment.active.read();
429 if current >= compartment.capacity {
430 *compartment.rejected.write() += 1;
431 return Err(FaultToleranceError::BulkheadFull {
432 compartment: compartment_id.to_string(),
433 capacity: compartment.capacity,
434 });
435 }
436 *compartment.active.write() += 1;
437 *compartment.accepted.write() += 1;
438 debug!(
439 "Bulkhead permit acquired for compartment {} ({}/{})",
440 compartment_id,
441 current + 1,
442 compartment.capacity
443 );
444
445 Ok(BulkheadPermit {
446 compartment_id: compartment_id.to_string(),
447 active_counter: Arc::clone(&compartment.active),
448 })
449 }
450
451 pub fn stats(&self) -> Vec<CompartmentStats> {
453 self.compartments
454 .read()
455 .iter()
456 .map(|(id, c)| CompartmentStats {
457 compartment_id: id.clone(),
458 capacity: c.capacity,
459 active: *c.active.read(),
460 rejected: *c.rejected.read(),
461 accepted: *c.accepted.read(),
462 })
463 .collect()
464 }
465
466 pub fn compartment_stats(&self, compartment_id: &str) -> Option<CompartmentStats> {
468 self.compartments
469 .read()
470 .get(compartment_id)
471 .map(|c| CompartmentStats {
472 compartment_id: compartment_id.to_string(),
473 capacity: c.capacity,
474 active: *c.active.read(),
475 rejected: *c.rejected.read(),
476 accepted: *c.accepted.read(),
477 })
478 }
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct StreamRetryPolicy {
486 pub max_attempts: u32,
488 pub initial_delay: Duration,
490 pub backoff_multiplier: f64,
492 pub max_delay: Duration,
494 pub jitter: bool,
496}
497
498impl Default for StreamRetryPolicy {
499 fn default() -> Self {
500 Self {
501 max_attempts: 3,
502 initial_delay: Duration::from_millis(100),
503 backoff_multiplier: 2.0,
504 max_delay: Duration::from_secs(30),
505 jitter: true,
506 }
507 }
508}
509
510impl StreamRetryPolicy {
511 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
516 let factor = self.backoff_multiplier.powi(attempt as i32);
517 let base_ms = self.initial_delay.as_millis() as f64 * factor;
518 let capped_ms = base_ms.min(self.max_delay.as_millis() as f64);
519
520 let jitter_ms = if self.jitter {
521 let pseudo = ((attempt as u64)
523 .wrapping_mul(6364136223846793005)
524 .wrapping_add(1))
525 % 1000;
526 let ratio = pseudo as f64 / 4000.0; capped_ms * ratio
528 } else {
529 0.0
530 };
531
532 Duration::from_millis((capped_ms + jitter_ms) as u64)
533 }
534
535 pub fn retry<F, T, E>(&self, operation_name: &str, mut f: F) -> FaultResult<T>
540 where
541 F: FnMut() -> Result<T, E>,
542 E: std::fmt::Debug,
543 {
544 for attempt in 0..=self.max_attempts {
545 match f() {
546 Ok(result) => {
547 if attempt > 0 {
548 info!(
549 "Operation {} succeeded after {} retries",
550 operation_name, attempt
551 );
552 }
553 return Ok(result);
554 }
555 Err(err) => {
556 if attempt >= self.max_attempts {
557 warn!(
558 "Operation {} failed after {} attempts: {:?}",
559 operation_name,
560 attempt + 1,
561 err
562 );
563 return Err(FaultToleranceError::MaxRetriesExceeded {
564 attempts: attempt + 1,
565 operation: operation_name.to_string(),
566 });
567 }
568 let delay = self.delay_for_attempt(attempt);
569 debug!(
570 "Operation {} attempt {} failed, retrying in {:?}",
571 operation_name,
572 attempt + 1,
573 delay
574 );
575 std::thread::sleep(delay);
576 }
577 }
578 }
579 Err(FaultToleranceError::MaxRetriesExceeded {
581 attempts: self.max_attempts + 1,
582 operation: operation_name.to_string(),
583 })
584 }
585
586 pub async fn retry_async<F, Fut, T, E>(&self, operation_name: &str, mut f: F) -> FaultResult<T>
588 where
589 F: FnMut() -> Fut,
590 Fut: std::future::Future<Output = Result<T, E>>,
591 E: std::fmt::Debug,
592 {
593 for attempt in 0..=self.max_attempts {
594 match f().await {
595 Ok(result) => {
596 if attempt > 0 {
597 info!(
598 "Async operation {} succeeded after {} retries",
599 operation_name, attempt
600 );
601 }
602 return Ok(result);
603 }
604 Err(err) => {
605 if attempt >= self.max_attempts {
606 warn!(
607 "Async operation {} failed after {} attempts: {:?}",
608 operation_name,
609 attempt + 1,
610 err
611 );
612 return Err(FaultToleranceError::MaxRetriesExceeded {
613 attempts: attempt + 1,
614 operation: operation_name.to_string(),
615 });
616 }
617 let delay = self.delay_for_attempt(attempt);
618 debug!(
619 "Async operation {} attempt {} failed, retrying in {:?}",
620 operation_name,
621 attempt + 1,
622 delay
623 );
624 tokio::time::sleep(delay).await;
625 }
626 }
627 }
628 Err(FaultToleranceError::MaxRetriesExceeded {
629 attempts: self.max_attempts + 1,
630 operation: operation_name.to_string(),
631 })
632 }
633}
634
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
639pub enum WorkerStatus {
640 Running,
642 Failed,
644 Restarting,
646 Stopped,
648 Exhausted,
650}
651
652#[derive(Debug, Clone, Serialize, Deserialize)]
654pub struct RestartRecord {
655 pub worker_id: String,
657 pub attempt: u32,
659 pub reason: String,
661 pub restarted_at: SystemTime,
663 pub success: bool,
665}
666
667#[derive(Debug, Clone)]
669struct WorkerState {
670 worker_id: String,
671 status: WorkerStatus,
672 restart_count: u32,
673 max_restarts: u32,
674 last_failure: Option<SystemTime>,
675 last_restart: Option<SystemTime>,
676}
677
678#[derive(Debug, Clone, Serialize, Deserialize)]
680pub struct SupervisorConfig {
681 pub max_restarts: u32,
683 pub restart_policy: StreamRetryPolicy,
685 pub one_for_all: bool,
687}
688
689impl Default for SupervisorConfig {
690 fn default() -> Self {
691 Self {
692 max_restarts: 5,
693 restart_policy: StreamRetryPolicy {
694 max_attempts: 5,
695 initial_delay: Duration::from_millis(500),
696 backoff_multiplier: 2.0,
697 max_delay: Duration::from_secs(60),
698 jitter: true,
699 },
700 one_for_all: false,
701 }
702 }
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct SupervisorStats {
708 pub total_workers: usize,
710 pub running_workers: usize,
712 pub exhausted_workers: usize,
714 pub total_restarts: u64,
716 pub restart_history_len: usize,
718}
719
720pub struct StreamSupervisor {
725 config: SupervisorConfig,
726 workers: Arc<RwLock<HashMap<String, WorkerState>>>,
727 restart_history: Arc<RwLock<Vec<RestartRecord>>>,
728 total_restarts: Arc<RwLock<u64>>,
729}
730
731impl StreamSupervisor {
732 pub fn new(config: SupervisorConfig) -> Self {
734 Self {
735 config,
736 workers: Arc::new(RwLock::new(HashMap::new())),
737 restart_history: Arc::new(RwLock::new(Vec::new())),
738 total_restarts: Arc::new(RwLock::new(0)),
739 }
740 }
741
742 pub fn register_worker(&self, worker_id: impl Into<String>) {
744 let id = worker_id.into();
745 self.workers.write().insert(
746 id.clone(),
747 WorkerState {
748 worker_id: id,
749 status: WorkerStatus::Running,
750 restart_count: 0,
751 max_restarts: self.config.max_restarts,
752 last_failure: None,
753 last_restart: None,
754 },
755 );
756 }
757
758 pub fn report_failure(&self, worker_id: &str, reason: &str) -> FaultResult<WorkerStatus> {
765 let new_status = {
766 let mut workers = self.workers.write();
767 let worker = workers.get_mut(worker_id).ok_or_else(|| {
768 FaultToleranceError::SupervisorRestartFailed {
769 worker_id: worker_id.to_string(),
770 attempts: 0,
771 }
772 })?;
773
774 worker.last_failure = Some(SystemTime::now());
775
776 if worker.restart_count >= worker.max_restarts {
777 worker.status = WorkerStatus::Exhausted;
778 warn!(
779 "Worker {} permanently failed after {} restarts",
780 worker_id, worker.restart_count
781 );
782 WorkerStatus::Exhausted
783 } else {
784 worker.status = WorkerStatus::Restarting;
785 worker.restart_count += 1;
786 worker.last_restart = Some(SystemTime::now());
787 WorkerStatus::Restarting
788 }
789 };
790
791 let attempt = self
793 .workers
794 .read()
795 .get(worker_id)
796 .map(|w| w.restart_count)
797 .unwrap_or(0);
798 let record = RestartRecord {
799 worker_id: worker_id.to_string(),
800 attempt,
801 reason: reason.to_string(),
802 restarted_at: SystemTime::now(),
803 success: new_status == WorkerStatus::Restarting,
804 };
805 let mut history = self.restart_history.write();
806 if history.len() >= 10_000 {
807 history.remove(0);
808 }
809 history.push(record);
810
811 if new_status == WorkerStatus::Restarting {
812 *self.total_restarts.write() += 1;
813 info!("Restarting worker {} (attempt {})", worker_id, attempt);
814
815 if self.config.one_for_all {
817 let siblings: Vec<String> = self
818 .workers
819 .read()
820 .keys()
821 .filter(|k| k.as_str() != worker_id)
822 .cloned()
823 .collect();
824 for sibling_id in siblings {
825 let mut workers = self.workers.write();
826 if let Some(sibling) = workers.get_mut(&sibling_id) {
827 if sibling.status == WorkerStatus::Running {
828 sibling.status = WorkerStatus::Restarting;
829 sibling.restart_count += 1;
830 sibling.last_restart = Some(SystemTime::now());
831 }
832 }
833 }
834 }
835 }
836
837 Ok(new_status)
838 }
839
840 pub fn acknowledge_restart(&self, worker_id: &str) -> FaultResult<()> {
842 let mut workers = self.workers.write();
843 let worker = workers.get_mut(worker_id).ok_or_else(|| {
844 FaultToleranceError::SupervisorRestartFailed {
845 worker_id: worker_id.to_string(),
846 attempts: 0,
847 }
848 })?;
849 worker.status = WorkerStatus::Running;
850 info!("Worker {} successfully restarted", worker_id);
851 Ok(())
852 }
853
854 pub fn stop_worker(&self, worker_id: &str) -> FaultResult<()> {
856 let mut workers = self.workers.write();
857 let worker = workers.get_mut(worker_id).ok_or_else(|| {
858 FaultToleranceError::SupervisorRestartFailed {
859 worker_id: worker_id.to_string(),
860 attempts: 0,
861 }
862 })?;
863 worker.status = WorkerStatus::Stopped;
864 info!("Worker {} stopped", worker_id);
865 Ok(())
866 }
867
868 pub fn worker_status(&self, worker_id: &str) -> Option<WorkerStatus> {
870 self.workers.read().get(worker_id).map(|w| w.status.clone())
871 }
872
873 pub fn workers_with_status(&self, status: &WorkerStatus) -> Vec<String> {
875 self.workers
876 .read()
877 .values()
878 .filter(|w| &w.status == status)
879 .map(|w| w.worker_id.clone())
880 .collect()
881 }
882
883 pub fn stats(&self) -> SupervisorStats {
885 let workers = self.workers.read();
886 let running_workers = workers
887 .values()
888 .filter(|w| w.status == WorkerStatus::Running)
889 .count();
890 let exhausted_workers = workers
891 .values()
892 .filter(|w| w.status == WorkerStatus::Exhausted)
893 .count();
894 SupervisorStats {
895 total_workers: workers.len(),
896 running_workers,
897 exhausted_workers,
898 total_restarts: *self.total_restarts.read(),
899 restart_history_len: self.restart_history.read().len(),
900 }
901 }
902
903 pub fn restart_history(&self) -> Vec<RestartRecord> {
905 self.restart_history.read().clone()
906 }
907}
908
909#[cfg(test)]
912mod tests {
913 use super::*;
914
915 #[test]
918 fn test_health_monitor_healthy_state() {
919 let config = HealthMonitorConfig::default();
920 let monitor = StreamHealthMonitor::new(config);
921 monitor.record_metric("error_rate", 0.001);
922 monitor.record_metric("latency_p99_ms", 50.0);
923 monitor.record_metric("backpressure_ratio", 0.1);
924
925 let snap = monitor.snapshot();
926 assert_eq!(snap.status, StreamHealthStatus::Healthy);
927 assert!(snap.active_alerts.is_empty());
928 }
929
930 #[test]
931 fn test_health_monitor_warning_alert() {
932 let config = HealthMonitorConfig::default();
933 let monitor = StreamHealthMonitor::new(config);
934
935 let alerts = monitor.record_metric("error_rate", 0.02);
936 assert_eq!(alerts.len(), 1);
937 assert_eq!(alerts[0].severity, HealthAlertSeverity::Warning);
938
939 let snap = monitor.snapshot();
940 assert_eq!(snap.status, StreamHealthStatus::Degraded);
941 }
942
943 #[test]
944 fn test_health_monitor_critical_alert() {
945 let config = HealthMonitorConfig::default();
946 let monitor = StreamHealthMonitor::new(config);
947
948 let alerts = monitor.record_metric("error_rate", 0.10);
949 assert_eq!(alerts.len(), 1);
950 assert_eq!(alerts[0].severity, HealthAlertSeverity::Critical);
951
952 let snap = monitor.snapshot();
953 assert_eq!(snap.status, StreamHealthStatus::Critical);
954 }
955
956 #[test]
957 fn test_health_monitor_recovery() {
958 let config = HealthMonitorConfig::default();
959 let monitor = StreamHealthMonitor::new(config);
960
961 monitor.record_metric("error_rate", 0.10); let snap = monitor.snapshot();
963 assert_eq!(snap.status, StreamHealthStatus::Critical);
964
965 monitor.record_metric("error_rate", 0.001); let snap = monitor.snapshot();
967 assert!(snap.active_alerts.is_empty());
968 }
969
970 #[test]
971 fn test_health_monitor_total_alerts_count() {
972 let config = HealthMonitorConfig::default();
973 let monitor = StreamHealthMonitor::new(config);
974 monitor.record_metric("error_rate", 0.02);
975 monitor.record_metric("latency_p99_ms", 200.0);
976 assert_eq!(monitor.total_alerts_raised(), 2);
977 }
978
979 #[test]
982 fn test_bulkhead_acquire_and_release() {
983 let mut config = BulkheadConfig::default();
984 config.compartment_capacities.insert("test".to_string(), 2);
985 let isolator = BulkheadIsolator::new(config);
986
987 let p1 = isolator
988 .acquire("test")
989 .expect("first permit should succeed");
990 let p2 = isolator
991 .acquire("test")
992 .expect("second permit should succeed");
993
994 let result = isolator.acquire("test");
995 assert!(
996 matches!(result, Err(FaultToleranceError::BulkheadFull { .. })),
997 "third permit should be rejected"
998 );
999
1000 let stats = isolator
1001 .compartment_stats("test")
1002 .expect("stats should exist");
1003 assert_eq!(stats.active, 2);
1004 assert_eq!(stats.rejected, 1);
1005
1006 drop(p1);
1007 drop(p2);
1008
1009 let stats = isolator
1010 .compartment_stats("test")
1011 .expect("stats should exist");
1012 assert_eq!(stats.active, 0);
1013 }
1014
1015 #[test]
1016 fn test_bulkhead_auto_creates_compartment() {
1017 let config = BulkheadConfig {
1018 compartment_capacities: HashMap::new(),
1019 default_capacity: 5,
1020 };
1021 let isolator = BulkheadIsolator::new(config);
1022 let permit = isolator
1023 .acquire("new-compartment")
1024 .expect("should succeed with default capacity");
1025 drop(permit);
1026 }
1027
1028 #[test]
1029 fn test_bulkhead_different_compartments_isolated() {
1030 let mut config = BulkheadConfig::default();
1031 config.compartment_capacities.insert("a".to_string(), 1);
1032 config.compartment_capacities.insert("b".to_string(), 1);
1033 let isolator = BulkheadIsolator::new(config);
1034
1035 let _pa = isolator.acquire("a").expect("a should succeed");
1036 let result_a = isolator.acquire("a");
1038 assert!(matches!(
1039 result_a,
1040 Err(FaultToleranceError::BulkheadFull { .. })
1041 ));
1042
1043 let _pb = isolator.acquire("b").expect("b should be independent");
1045 }
1046
1047 #[test]
1050 fn test_retry_policy_delay_increases() {
1051 let policy = StreamRetryPolicy {
1052 max_attempts: 5,
1053 initial_delay: Duration::from_millis(100),
1054 backoff_multiplier: 2.0,
1055 max_delay: Duration::from_secs(60),
1056 jitter: false,
1057 };
1058 let d0 = policy.delay_for_attempt(0);
1059 let d1 = policy.delay_for_attempt(1);
1060 let d2 = policy.delay_for_attempt(2);
1061 assert!(d0 < d1, "delay should increase");
1062 assert!(d1 < d2, "delay should increase");
1063 }
1064
1065 #[test]
1066 fn test_retry_policy_max_delay_cap() {
1067 let policy = StreamRetryPolicy {
1068 max_attempts: 10,
1069 initial_delay: Duration::from_millis(100),
1070 backoff_multiplier: 10.0,
1071 max_delay: Duration::from_millis(500),
1072 jitter: false,
1073 };
1074 let d = policy.delay_for_attempt(5);
1075 assert!(
1076 d <= Duration::from_millis(500) + Duration::from_millis(10),
1077 "delay should not exceed max"
1078 );
1079 }
1080
1081 #[test]
1082 fn test_retry_succeeds_on_first_attempt() {
1083 let policy = StreamRetryPolicy {
1084 max_attempts: 3,
1085 initial_delay: Duration::from_millis(1),
1086 backoff_multiplier: 2.0,
1087 max_delay: Duration::from_secs(1),
1088 jitter: false,
1089 };
1090 let result: FaultResult<i32> = policy.retry("test-op", || Ok::<i32, &str>(42));
1091 assert!(matches!(result, Ok(42)));
1092 }
1093
1094 #[test]
1095 fn test_retry_exhausts_attempts() {
1096 let policy = StreamRetryPolicy {
1097 max_attempts: 2,
1098 initial_delay: Duration::from_millis(1),
1099 backoff_multiplier: 1.0,
1100 max_delay: Duration::from_millis(5),
1101 jitter: false,
1102 };
1103 let mut calls = 0u32;
1104 let result: FaultResult<i32> = policy.retry("always-fail", || {
1105 calls += 1;
1106 Err::<i32, &str>("always fails")
1107 });
1108 assert!(matches!(
1109 result,
1110 Err(FaultToleranceError::MaxRetriesExceeded { .. })
1111 ));
1112 assert_eq!(calls, 3);
1114 }
1115
1116 #[test]
1117 fn test_retry_succeeds_after_failures() {
1118 let policy = StreamRetryPolicy {
1119 max_attempts: 5,
1120 initial_delay: Duration::from_millis(1),
1121 backoff_multiplier: 1.0,
1122 max_delay: Duration::from_millis(10),
1123 jitter: false,
1124 };
1125 let mut calls = 0u32;
1126 let result: FaultResult<i32> = policy.retry("eventually-succeeds", || {
1127 calls += 1;
1128 if calls < 3 {
1129 Err::<i32, &str>("not yet")
1130 } else {
1131 Ok(99)
1132 }
1133 });
1134 assert!(matches!(result, Ok(99)));
1135 assert_eq!(calls, 3);
1136 }
1137
1138 #[tokio::test]
1139 async fn test_retry_async_succeeds() {
1140 let policy = StreamRetryPolicy {
1141 max_attempts: 3,
1142 initial_delay: Duration::from_millis(1),
1143 backoff_multiplier: 1.0,
1144 max_delay: Duration::from_millis(5),
1145 jitter: false,
1146 };
1147 let calls = Arc::new(RwLock::new(0u32));
1148 let calls_clone = Arc::clone(&calls);
1149 let result: FaultResult<i32> = policy
1150 .retry_async("async-op", move || {
1151 let c = Arc::clone(&calls_clone);
1152 async move {
1153 let mut lock = c.write();
1154 *lock += 1;
1155 let v = *lock;
1156 drop(lock);
1157 if v < 2 {
1158 Err::<i32, &str>("not ready")
1159 } else {
1160 Ok(7)
1161 }
1162 }
1163 })
1164 .await;
1165 assert!(matches!(result, Ok(7)));
1166 }
1167
1168 #[test]
1171 fn test_supervisor_register_and_failure_restart() {
1172 let config = SupervisorConfig::default();
1173 let supervisor = StreamSupervisor::new(config);
1174 supervisor.register_worker("worker-1");
1175
1176 let status = supervisor
1177 .report_failure("worker-1", "connection lost")
1178 .expect("should handle failure");
1179 assert_eq!(status, WorkerStatus::Restarting);
1180
1181 supervisor
1182 .acknowledge_restart("worker-1")
1183 .expect("ack should succeed");
1184 assert_eq!(
1185 supervisor.worker_status("worker-1"),
1186 Some(WorkerStatus::Running)
1187 );
1188 }
1189
1190 #[test]
1191 fn test_supervisor_exhausted_after_max_restarts() {
1192 let config = SupervisorConfig {
1193 max_restarts: 2,
1194 ..Default::default()
1195 };
1196 let supervisor = StreamSupervisor::new(config);
1197 supervisor.register_worker("worker-x");
1198
1199 for _ in 0..2 {
1200 let status = supervisor
1201 .report_failure("worker-x", "crash")
1202 .expect("failure should be handled");
1203 if status == WorkerStatus::Restarting {
1204 supervisor.acknowledge_restart("worker-x").ok();
1205 }
1206 }
1207
1208 let final_status = supervisor
1209 .report_failure("worker-x", "final crash")
1210 .expect("final failure should be handled");
1211 assert_eq!(final_status, WorkerStatus::Exhausted);
1212
1213 let stats = supervisor.stats();
1214 assert_eq!(stats.exhausted_workers, 1);
1215 }
1216
1217 #[test]
1218 fn test_supervisor_stop_worker() {
1219 let config = SupervisorConfig::default();
1220 let supervisor = StreamSupervisor::new(config);
1221 supervisor.register_worker("w1");
1222 supervisor.stop_worker("w1").expect("stop should succeed");
1223 assert_eq!(supervisor.worker_status("w1"), Some(WorkerStatus::Stopped));
1224 }
1225
1226 #[test]
1227 fn test_supervisor_one_for_all() {
1228 let config = SupervisorConfig {
1229 max_restarts: 5,
1230 one_for_all: true,
1231 ..Default::default()
1232 };
1233 let supervisor = StreamSupervisor::new(config);
1234 supervisor.register_worker("w1");
1235 supervisor.register_worker("w2");
1236 supervisor.register_worker("w3");
1237
1238 supervisor
1239 .report_failure("w1", "cascade test")
1240 .expect("failure should be handled");
1241
1242 let restarting = supervisor.workers_with_status(&WorkerStatus::Restarting);
1244 assert!(
1246 restarting.len() >= 2,
1247 "siblings should also restart: {:?}",
1248 restarting
1249 );
1250 }
1251
1252 #[test]
1253 fn test_supervisor_restart_history() {
1254 let config = SupervisorConfig::default();
1255 let supervisor = StreamSupervisor::new(config);
1256 supervisor.register_worker("wh");
1257
1258 supervisor.report_failure("wh", "reason-1").ok();
1259 supervisor.acknowledge_restart("wh").ok();
1260 supervisor.report_failure("wh", "reason-2").ok();
1261
1262 let history = supervisor.restart_history();
1263 assert!(history.len() >= 2);
1264 assert_eq!(history[0].reason, "reason-1");
1265 }
1266
1267 #[test]
1268 fn test_supervisor_stats() {
1269 let config = SupervisorConfig::default();
1270 let supervisor = StreamSupervisor::new(config);
1271 supervisor.register_worker("s1");
1272 supervisor.register_worker("s2");
1273
1274 supervisor.report_failure("s1", "err").ok();
1275 supervisor.acknowledge_restart("s1").ok();
1276
1277 let stats = supervisor.stats();
1278 assert_eq!(stats.total_workers, 2);
1279 assert_eq!(stats.running_workers, 2); assert_eq!(stats.total_restarts, 1);
1281 }
1282}