1use super::supervision::{SupervisionParams, supervise};
8use super::types::{DepSetupReceivers, PrerequisiteFuture, SupervisionResult, TaskEntry};
9use super::validation::validate_dependencies;
10use crate::contracts::{SupervisedTask, SupervisorEventListener};
11use crate::enums::{ControlMessage, HealthStatus, SupervisorEvent, TaskConfig};
12use crate::error::{SupervisorError, SupervisorResult};
13use crate::persistence::TaskStateStore;
14use std::collections::{HashMap, HashSet};
15use std::fmt;
16use std::future::Future;
17use std::sync::Arc;
18use std::time::Duration;
19use tokio::sync::{RwLock, Semaphore, broadcast, watch};
20use tokio::task::JoinHandle;
21use tracing::{error, info, warn};
22
23#[cfg(feature = "cron")]
24use foxtive_cron::Cron;
25
26pub struct TaskRuntime {
39 pub(super) tasks: HashMap<&'static str, TaskEntry>,
40 pub(super) handles: HashMap<&'static str, JoinHandle<SupervisionResult>>,
41 pub(super) prerequisites: Vec<(&'static str, PrerequisiteFuture)>,
43 pub(super) setup_signals: HashMap<&'static str, watch::Sender<Option<Result<(), String>>>>,
45 pub(super) event_tx: broadcast::Sender<SupervisorEvent>,
47 pub(super) listeners: Vec<Arc<dyn SupervisorEventListener>>,
49 pub(super) state_store: Option<Arc<dyn TaskStateStore>>,
51 pub(super) global_concurrency_limit: Option<Arc<Semaphore>>,
53 pub(crate) task_concurrency_limits: HashMap<&'static str, Arc<Semaphore>>,
55 pub(super) task_configs: HashMap<&'static str, Arc<RwLock<TaskConfig>>>,
57 #[cfg(feature = "cron")]
58 #[allow(dead_code)]
59 pub(super) cron: Option<Arc<tokio::sync::Mutex<Cron>>>,
60 #[cfg(feature = "foxtive-app")]
62 pub(super) app: Option<Arc<foxtive::App>>,
63 pub(super) event_channel_capacity: usize,
65}
66
67impl fmt::Debug for TaskRuntime {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.debug_struct("TaskRuntime")
70 .field("tasks_count", &self.tasks.len())
71 .field("handles_count", &self.handles.len())
72 .field("prerequisites_count", &self.prerequisites.len())
73 .finish()
74 }
75}
76
77impl TaskRuntime {
78 pub fn new() -> Self {
80 let event_channel_capacity = 1024;
81 let (event_tx, _) = broadcast::channel(event_channel_capacity);
82 #[allow(unused_mut)]
83 let mut runtime = Self {
84 tasks: HashMap::new(),
85 handles: HashMap::new(),
86 prerequisites: Vec::new(),
87 setup_signals: HashMap::new(),
88 event_tx,
89 listeners: Vec::new(),
90 state_store: None,
91 global_concurrency_limit: None,
92 task_concurrency_limits: HashMap::new(),
93 task_configs: HashMap::new(),
94 #[cfg(feature = "cron")]
95 cron: None,
96 #[cfg(feature = "foxtive-app")]
97 app: None,
98 event_channel_capacity,
99 };
100 runtime
101 }
102
103 pub fn with_global_concurrency_limit(&mut self, limit: usize) -> &mut Self {
110 self.global_concurrency_limit = Some(Arc::new(Semaphore::new(limit)));
111 self
112 }
113
114 pub fn with_event_channel_capacity(&mut self, capacity: usize) -> &mut Self {
119 self.event_channel_capacity = capacity;
120 let (event_tx, _) = broadcast::channel(capacity);
122 self.event_tx = event_tx;
123 self
124 }
125
126 #[cfg(feature = "foxtive-app")]
128 pub fn with_app(&mut self, app: Arc<foxtive::App>) -> &mut Self {
129 self.app = Some(app);
130 self
131 }
132
133 #[cfg(feature = "foxtive-app")]
135 pub fn app(&self) -> Option<&Arc<foxtive::App>> {
136 self.app.as_ref()
137 }
138
139 pub fn register<T: SupervisedTask + 'static>(&mut self, task: T) -> &mut Self {
146 let (setup_tx, _) = watch::channel(None);
147 let (control_tx, _) = broadcast::channel(64);
148 let id = task.id();
149
150 if let Some(limit) = task.concurrency_limit() {
152 self.task_concurrency_limits
153 .insert(id, Arc::new(Semaphore::new(limit)));
154 }
155
156 let config = TaskConfig::from_task(&task);
158 self.task_configs.insert(id, Arc::new(RwLock::new(config)));
159
160 self.setup_signals.insert(id, setup_tx.clone());
161 self.tasks.insert(
162 id,
163 TaskEntry {
164 task: Arc::new(task),
165 setup_tx,
166 control_tx,
167 },
168 );
169 self
171 }
172
173 pub fn register_many<T: SupervisedTask + 'static>(&mut self, tasks: Vec<T>) -> &mut Self {
175 for task in tasks {
176 self.register(task);
177 }
178 self
179 }
180
181 pub fn register_boxed(&mut self, task: Box<dyn SupervisedTask>) -> &mut Self {
185 let (setup_tx, _) = watch::channel(None);
186 let (control_tx, _) = broadcast::channel(64);
187 let id = task.id();
188
189 if let Some(limit) = task.concurrency_limit() {
190 self.task_concurrency_limits
191 .insert(id, Arc::new(Semaphore::new(limit)));
192 }
193
194 let config = TaskConfig::from_task(task.as_ref());
196 self.task_configs.insert(id, Arc::new(RwLock::new(config)));
197
198 self.setup_signals.insert(id, setup_tx.clone());
199 self.tasks.insert(
200 id,
201 TaskEntry {
202 task: Arc::from(task),
203 setup_tx,
204 control_tx,
205 },
206 );
207 self
209 }
210
211 pub fn register_arc(&mut self, task: Arc<dyn SupervisedTask>) -> &mut Self {
215 let (setup_tx, _) = watch::channel(None);
216 let (control_tx, _) = broadcast::channel(64);
217 let id = task.id();
218
219 if let Some(limit) = task.concurrency_limit() {
220 self.task_concurrency_limits
221 .insert(id, Arc::new(Semaphore::new(limit)));
222 }
223
224 let config = TaskConfig::from_task(task.as_ref());
226 self.task_configs.insert(id, Arc::new(RwLock::new(config)));
227
228 self.setup_signals.insert(id, setup_tx.clone());
229 self.tasks.insert(
230 id,
231 TaskEntry {
232 task,
233 setup_tx,
234 control_tx,
235 },
236 );
237 self
239 }
240
241 pub fn add_task<T: SupervisedTask + 'static>(
251 &mut self,
252 task: T,
253 ) -> Result<(), SupervisorError> {
254 let id = task.id();
255 if self.tasks.contains_key(id) {
256 return Err(SupervisorError::config(
257 id,
258 "Task already exists",
259 ));
260 }
261
262 self.register(task);
263
264 let tasks_vec: Vec<&TaskEntry> = self.tasks.values().collect();
266 validate_dependencies(&tasks_vec)?;
267
268 let entry = self.tasks.get(id).unwrap();
269
270 let mut dep_receivers = Vec::new();
272 let active_deps = entry.task.active_dependencies();
273 for dep_id in &active_deps {
274 if let Some(sender) = self.setup_signals.get(dep_id) {
275 dep_receivers.push((*dep_id, sender.subscribe()));
276 } else {
277 return Err(SupervisorError::dependency_validation(
278 id,
279 dep_id,
280 crate::error::ValidationError::UnknownTaskId,
281 ));
282 }
283 }
284
285 let task_limit = self.task_concurrency_limits.get(id).cloned();
286 let task_config = self.task_configs.get(id).cloned();
287
288 let params = SupervisionParams {
289 task: entry.task.clone(),
290 setup_tx: entry.setup_tx.clone(),
291 control_rx: entry.control_tx.subscribe(),
292 event_tx: self.event_tx.clone(),
293 dep_receivers,
294 state_store: self.state_store.clone(),
295 global_semaphore: self.global_concurrency_limit.clone(),
296 task_semaphore: task_limit,
297 task_config,
298 };
299
300 let handle = supervise(params);
301 self.handles.insert(id, handle);
302
303 let name = entry.task.name();
305 let _ = self.event_tx.send(SupervisorEvent::TaskRegistered {
306 id: id.to_string(),
307 name,
308 });
309
310 Ok(())
311 }
312
313 pub fn restart_task(&self, id: &str) -> Result<(), SupervisorError> {
320 if let Some(entry) = self.tasks.get(id) {
321 let _ = entry.control_tx.send(ControlMessage::Restart);
322 Ok(())
323 } else {
324 Err(SupervisorError::UnknownTask(id.to_string()))
325 }
326 }
327
328 pub fn pause_task(&self, id: &str) -> Result<(), SupervisorError> {
336 if let Some(entry) = self.tasks.get(id) {
337 let _ = entry.control_tx.send(ControlMessage::Pause);
338 Ok(())
339 } else {
340 Err(SupervisorError::UnknownTask(id.to_string()))
341 }
342 }
343
344 pub fn resume_task(&self, id: &str) -> Result<(), SupervisorError> {
352 if let Some(entry) = self.tasks.get(id) {
353 let _ = entry.control_tx.send(ControlMessage::Resume);
354 Ok(())
355 } else {
356 Err(SupervisorError::UnknownTask(id.to_string()))
357 }
358 }
359
360 pub fn reset_circuit_breaker(&self, id: &str) -> Result<(), SupervisorError> {
365 if let Some(entry) = self.tasks.get(id) {
366 let _ = entry.control_tx.send(ControlMessage::ResetCircuitBreaker);
367 Ok(())
368 } else {
369 Err(SupervisorError::UnknownTask(id.to_string()))
370 }
371 }
372
373 pub async fn update_restart_policy(
387 &self,
388 id: &str,
389 new_policy: crate::enums::RestartPolicy,
390 ) -> Result<(), SupervisorError> {
391 let config_lock = self
392 .task_configs
393 .get(id)
394 .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
395
396 Self::validate_restart_policy(&new_policy)?;
398
399 let mut config = config_lock.write().await;
400 let old_policy = config.restart_policy.clone();
401 config.restart_policy = new_policy.clone();
402
403 if let Some(entry) = self.tasks.get(id) {
405 let name = entry.task.name();
406 let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
407 id: id.to_string(),
408 name,
409 field: "restart_policy".to_string(),
410 old_value: format!("{:?}", old_policy),
411 new_value: format!("{:?}", new_policy),
412 });
413 }
414
415 info!(task_id = %id, ?old_policy, ?new_policy, "Updated restart policy");
416 Ok(())
417 }
418
419 pub async fn update_backoff_strategy(
431 &self,
432 id: &str,
433 new_strategy: crate::enums::BackoffStrategy,
434 ) -> Result<(), SupervisorError> {
435 let config_lock = self
436 .task_configs
437 .get(id)
438 .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
439
440 Self::validate_backoff_strategy(&new_strategy)?;
442
443 let mut config = config_lock.write().await;
444 let old_strategy = config.backoff_strategy.clone();
445 config.backoff_strategy = new_strategy.clone();
446
447 if let Some(entry) = self.tasks.get(id) {
449 let name = entry.task.name();
450 let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
451 id: id.to_string(),
452 name,
453 field: "backoff_strategy".to_string(),
454 old_value: format!("{:?}", old_strategy),
455 new_value: format!("{:?}", new_strategy),
456 });
457 }
458
459 info!(task_id = %id, ?old_strategy, ?new_strategy, "Updated backoff strategy");
460 Ok(())
461 }
462
463 pub async fn set_task_enabled(&self, id: &str, enabled: bool) -> Result<(), SupervisorError> {
475 let config_lock = self
476 .task_configs
477 .get(id)
478 .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
479
480 let mut config = config_lock.write().await;
481 let old_enabled = config.enabled;
482 config.enabled = enabled;
483
484 if let Some(entry) = self.tasks.get(id) {
486 let name = entry.task.name();
487 let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
488 id: id.to_string(),
489 name,
490 field: "enabled".to_string(),
491 old_value: old_enabled.to_string(),
492 new_value: enabled.to_string(),
493 });
494 }
495
496 info!(task_id = %id, old_enabled = old_enabled, new_enabled = enabled, "Updated task enabled status");
497 Ok(())
498 }
499
500 pub async fn get_task_config(&self, id: &str) -> Option<TaskConfig> {
508 let config_lock = self.task_configs.get(id)?;
509 let config = config_lock.read().await;
510 Some(config.clone())
511 }
512
513 pub async fn is_task_enabled(&self, id: &str) -> bool {
521 if let Some(config_lock) = self.task_configs.get(id) {
522 let config = config_lock.read().await;
523 config.enabled
524 } else {
525 false
526 }
527 }
528
529 pub fn start_group(&mut self, group_id: &str) -> usize {
542 let mut started_count = 0;
543
544 let group_task_ids: Vec<&'static str> = self
546 .tasks
547 .iter()
548 .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
549 .map(|(id, _)| *id)
550 .collect();
551
552 for task_id in group_task_ids {
553 if !self.handles.contains_key(task_id) {
554 if let Err(e) = self.add_task_by_id(task_id) {
556 error!(task_id = %task_id, error = ?e, "Failed to start task in group");
557 } else {
558 started_count += 1;
559 }
560 }
561 }
562
563 info!(group_id = %group_id, started = started_count, "Started task group");
564 started_count
565 }
566
567 pub async fn stop_group(&mut self, group_id: &str) -> usize {
577 let mut stopped_count = 0;
578
579 let group_task_ids: Vec<&'static str> = self
581 .tasks
582 .iter()
583 .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
584 .map(|(id, _)| *id)
585 .collect();
586
587 for task_id in group_task_ids {
588 if let Some(handle) = self.handles.remove(task_id) {
589 if let Some(entry) = self.tasks.get(task_id) {
590 let _ = entry.control_tx.send(ControlMessage::Stop);
591 }
592
593 match tokio::time::timeout(Duration::from_secs(5), handle).await {
595 Ok(_) => stopped_count += 1,
596 Err(_) => {
597 warn!(task_id = %task_id, "Task in group did not stop gracefully, aborting");
598 stopped_count += 1; }
600 }
601 }
602 }
603
604 info!(group_id = %group_id, stopped = stopped_count, "Stopped task group");
605 stopped_count
606 }
607
608 pub fn restart_group(&self, group_id: &str) -> usize {
618 let mut restarted_count = 0;
619
620 for (task_id, entry) in &self.tasks {
621 if entry.task.group_id() == Some(group_id) && self.handles.contains_key(task_id) {
622 let _ = entry.control_tx.send(ControlMessage::Restart);
623 restarted_count += 1;
624 }
625 }
626
627 info!(group_id = %group_id, restarted = restarted_count, "Restarted task group");
628 restarted_count
629 }
630
631 pub fn list_group_tasks(&self, group_id: &str) -> Vec<String> {
639 self.tasks
640 .iter()
641 .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
642 .map(|(id, _)| id.to_string())
643 .collect()
644 }
645
646 pub async fn get_group_health(&self, group_id: &str) -> HealthStatus {
660 let group_tasks: Vec<_> = self.tasks.values()
662 .filter(|entry| entry.task.group_id() == Some(group_id))
663 .collect();
664
665 if group_tasks.is_empty() {
666 return HealthStatus::Unknown;
667 }
668
669 let health_futures: Vec<_> = group_tasks.iter()
670 .map(|entry| entry.task.health_check())
671 .collect();
672 let results = futures_util::future::join_all(health_futures).await;
673
674 let mut has_healthy = false;
675 let mut has_degraded = false;
676 let mut has_unhealthy = false;
677
678 for health in results {
679 match health {
680 HealthStatus::Healthy => has_healthy = true,
681 HealthStatus::Degraded { .. } => has_degraded = true,
682 HealthStatus::Unhealthy { .. } => has_unhealthy = true,
683 HealthStatus::Unknown => {} }
685 }
686
687 if has_unhealthy {
689 HealthStatus::Unhealthy {
690 reason: "One or more tasks in group are unhealthy".to_string(),
691 }
692 } else if has_degraded {
693 HealthStatus::Degraded {
694 reason: "One or more tasks in group are degraded".to_string(),
695 }
696 } else if has_healthy {
697 HealthStatus::Healthy
698 } else {
699 HealthStatus::Unknown
700 }
701 }
702
703 pub async fn get_group_health_details(&self, group_id: &str) -> Vec<TaskSummary> {
711 let group_tasks: Vec<_> = self.tasks.iter()
713 .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
714 .collect();
715
716 let health_futures: Vec<_> = group_tasks.iter()
717 .map(|(task_id, entry)| async move {
718 let health = entry.task.health_check().await;
719 TaskSummary {
720 id: task_id.to_string(),
721 name: entry.task.name(),
722 health,
723 }
724 })
725 .collect();
726
727 futures_util::future::join_all(health_futures).await
728 }
729
730 fn add_task_by_id(&mut self, task_id: &'static str) -> Result<(), SupervisorError> {
732 let entry = self.tasks.get(task_id).unwrap();
733
734 let mut dep_receivers = Vec::new();
736 let active_deps = entry.task.active_dependencies();
737 for dep_id in &active_deps {
738 if let Some(sender) = self.setup_signals.get(dep_id) {
739 dep_receivers.push((*dep_id, sender.subscribe()));
740 } else {
741 return Err(SupervisorError::dependency_validation(
742 task_id,
743 dep_id,
744 crate::error::ValidationError::UnknownTaskId,
745 ));
746 }
747 }
748
749 let task_limit = self.task_concurrency_limits.get(task_id).cloned();
750 let task_config = self.task_configs.get(task_id).cloned();
751
752 let params = SupervisionParams {
753 task: entry.task.clone(),
754 setup_tx: entry.setup_tx.clone(),
755 control_rx: entry.control_tx.subscribe(),
756 event_tx: self.event_tx.clone(),
757 dep_receivers,
758 state_store: self.state_store.clone(),
759 global_semaphore: self.global_concurrency_limit.clone(),
760 task_semaphore: task_limit,
761 task_config,
762 };
763
764 let handle = supervise(params);
765 self.handles.insert(task_id, handle);
766
767 let name = entry.task.name();
768 let _ = self.event_tx.send(SupervisorEvent::TaskRegistered {
769 id: task_id.to_string(),
770 name,
771 });
772
773 Ok(())
774 }
775
776 pub async fn remove_task(
785 &mut self,
786 id: &str,
787 ) -> Result<Option<SupervisionResult>, SupervisorError> {
788 if let Some(entry) = self.tasks.remove(id) {
789 let _ = entry.control_tx.send(ControlMessage::Stop);
790 let name = entry.task.name();
791 self.setup_signals.remove(id);
792 let _ = self.event_tx.send(SupervisorEvent::TaskRemoved {
793 id: id.to_string(),
794 name,
795 });
796 if let Some(handle) = self.handles.remove(id) {
797 match handle.await {
798 Ok(res) => Ok(Some(res)),
799 Err(join_err) => Err(SupervisorError::task_panicked(id, join_err)),
800 }
801 } else {
802 Ok(None)
803 }
804 } else {
805 Err(SupervisorError::UnknownTask(id.to_string()))
806 }
807 }
808
809 pub async fn get_task_info(&self, id: &str) -> Result<TaskSummary, SupervisorError> {
816 if let Some(entry) = self.tasks.get(id) {
817 let health = entry.task.health_check().await;
818 Ok(TaskSummary {
819 id: id.to_string(),
820 name: entry.task.name(),
821 health,
822 })
823 } else {
824 Err(SupervisorError::UnknownTask(id.to_string()))
825 }
826 }
827
828 pub async fn list_tasks(&self) -> Vec<TaskSummary> {
830 let health_futures: Vec<_> = self.tasks.values()
832 .map(|entry| entry.task.health_check())
833 .collect();
834 let health_results = futures_util::future::join_all(health_futures).await;
835
836 self.tasks.iter()
837 .zip(health_results)
838 .map(|((id, entry), health)| TaskSummary {
839 id: id.to_string(),
840 name: entry.task.name(),
841 health,
842 })
843 .collect()
844 }
845
846 pub fn with_state_store(&mut self, store: Arc<dyn TaskStateStore>) -> &mut Self {
852 self.state_store = Some(store);
853 self
854 }
855
856 pub fn subscribe(&self) -> broadcast::Receiver<SupervisorEvent> {
863 self.event_tx.subscribe()
864 }
865
866 pub fn add_listener(&mut self, listener: Arc<dyn SupervisorEventListener>) -> &mut Self {
870 self.listeners.push(listener);
871 self
872 }
873
874 pub fn add_prerequisite(&mut self, name: &'static str, fut: PrerequisiteFuture) -> &mut Self {
880 self.prerequisites.push((name, fut));
881 self
882 }
883
884 pub fn add_prerequisite_fn<F, Fut>(&mut self, name: &'static str, f: F) -> &mut Self
888 where
889 F: FnOnce() -> Fut + Send + 'static,
890 Fut: Future<Output = SupervisorResult<()>> + Send + 'static,
891 {
892 self.add_prerequisite(name, Box::pin(async move { f().await }))
893 }
894
895 pub async fn start_all(&mut self) -> Result<(), SupervisorError> {
905 let mut event_rx = self.event_tx.subscribe();
907 let listeners = self.listeners.clone();
908 tokio::spawn(async move {
909 loop {
910 match event_rx.recv().await {
911 Ok(event) => {
912 for listener in &listeners {
913 listener.on_event(event.clone()).await;
914 }
915 }
916 Err(broadcast::error::RecvError::Lagged(n)) => {
917 warn!(
918 missed_events = n,
919 "Event channel lagged, skipping missed events to prevent listener backlog"
920 );
921 continue;
923 }
924 Err(broadcast::error::RecvError::Closed) => {
925 info!("Event channel closed, stopping event distribution");
926 break;
927 }
928 }
929 }
930 });
931
932 if !self.prerequisites.is_empty() {
934 info!(
935 "[Supervisor] Running {} prerequisites in parallel...",
936 self.prerequisites.len()
937 );
938
939 let prereq_futures: Vec<_> = self
940 .prerequisites
941 .drain(..)
942 .map(|(name, fut)| async move {
943 info!("[Supervisor] Awaiting prerequisite '{name}'...");
944 fut.await.map_err(|e| (name, e))
945 })
946 .collect();
947
948 let results = futures_util::future::try_join_all(prereq_futures)
949 .await
950 .map_err(|(name, e)| SupervisorError::prerequisite_failed(name, Box::new(e)))?;
951
952 info!("[Supervisor] All {} prerequisites satisfied", results.len());
953 }
954
955 if self.tasks.is_empty() {
956 warn!("[Supervisor] No tasks registered");
957 return Ok(());
958 }
959
960 let tasks_vec: Vec<&TaskEntry> = self.tasks.values().collect();
962 validate_dependencies(&tasks_vec)?;
963
964 info!(
965 "[Supervisor] Starting {} supervised tasks...",
966 self.tasks.len()
967 );
968
969 let mut sorted_ids: Vec<&'static str> = self.tasks.keys().copied().collect();
971 sorted_ids.sort_by(|a, b| {
972 let task_a = &self.tasks[a].task;
973 let task_b = &self.tasks[b].task;
974 task_b.priority().cmp(&task_a.priority())
975 });
976
977 for id in sorted_ids {
979 let entry = &self.tasks[id];
980 let name = entry.task.name();
982 let _ = self.event_tx.send(SupervisorEvent::TaskRegistered {
983 id: id.to_string(),
984 name,
985 });
986
987 let active_deps = entry.task.active_dependencies();
989 let dep_receivers: DepSetupReceivers = active_deps
990 .iter()
991 .map(|dep_id| {
992 let sender = self.setup_signals[dep_id].clone();
993 (*dep_id, sender.subscribe())
994 })
995 .collect();
996
997 let task_limit = self.task_concurrency_limits.get(id).cloned();
998 let task_config = self.task_configs.get(id).cloned();
999
1000 let params = SupervisionParams {
1001 task: entry.task.clone(),
1002 setup_tx: entry.setup_tx.clone(),
1003 control_rx: entry.control_tx.subscribe(),
1004 event_tx: self.event_tx.clone(),
1005 dep_receivers,
1006 state_store: self.state_store.clone(),
1007 global_semaphore: self.global_concurrency_limit.clone(),
1008 task_semaphore: task_limit,
1009 task_config,
1010 };
1011
1012 let handle = supervise(params);
1013 self.handles.insert(id, handle);
1014 }
1015
1016 info!("[Supervisor] All tasks started");
1017 Ok(())
1018 }
1019
1020 pub fn start_one<T: SupervisedTask + 'static>(task: T) -> JoinHandle<SupervisionResult> {
1025 let (setup_tx, _) = watch::channel(None);
1026 let (control_tx, _) = broadcast::channel(64);
1027 let (event_tx, _) = broadcast::channel(1);
1028 let params = SupervisionParams {
1029 task: Arc::new(task),
1030 setup_tx,
1031 control_rx: control_tx.subscribe(),
1032 event_tx,
1033 dep_receivers: vec![],
1034 state_store: None,
1035 global_semaphore: None,
1036 task_semaphore: None,
1037 task_config: None,
1038 };
1039 supervise(params)
1040 }
1041
1042 pub async fn wait_any(&mut self) -> SupervisionResult {
1048 if self.handles.is_empty() {
1049 warn!("[Supervisor] No tasks to wait for");
1050 return SupervisionResult {
1051 task_name: "none".to_string(),
1052 task_id: "none".to_string(),
1053 total_attempts: 0,
1054 final_status: crate::enums::SupervisionStatus::ManuallyStopped,
1055 };
1056 }
1057
1058 let mut handle_vec: Vec<(&'static str, JoinHandle<SupervisionResult>)> =
1060 self.handles.drain().collect();
1061
1062 use futures_util::future::select_all;
1064
1065 let mut futures = Vec::new();
1066 for (id, handle) in handle_vec.iter_mut() {
1067 let id_clone = *id;
1068 futures.push(Box::pin(async move {
1069 let result = handle.await;
1070 (id_clone, result)
1071 }));
1072 }
1073
1074 let ((finished_id, result), _idx, _remaining) = select_all(futures).await;
1075
1076 for (i, (id, handle)) in handle_vec.into_iter().enumerate() {
1078 if i != _idx {
1079 self.handles.insert(id, handle);
1080 }
1081 }
1082
1083 match result {
1084 Ok(supervision_result) => {
1085 error!(
1086 "[Supervisor] Task '{}' (id: {}) terminated: {:?}",
1087 supervision_result.task_name,
1088 supervision_result.task_id,
1089 supervision_result.final_status
1090 );
1091 supervision_result
1092 }
1093 Err(join_err) => {
1094 error!("[Supervisor] Task {} panicked: {:?}", finished_id, join_err);
1095 SupervisionResult {
1096 task_name: "unknown".to_string(),
1097 task_id: finished_id.to_string(),
1098 total_attempts: 0,
1099 final_status: crate::enums::SupervisionStatus::ManuallyStopped,
1100 }
1101 }
1102 }
1103 }
1104
1105 pub async fn wait_all(&mut self) -> Vec<SupervisionResult> {
1109 let mut results = Vec::new();
1110 while !self.handles.is_empty() {
1111 results.push(self.wait_any().await);
1112 }
1113 results
1114 }
1115
1116 pub async fn shutdown(mut self) {
1123 info!("[Supervisor] Shutting down {} tasks...", self.tasks.len());
1124 let _ = self
1125 .event_tx
1126 .send(SupervisorEvent::SupervisorShutdownStarted);
1127
1128 let shutdown_order = self.calculate_shutdown_order();
1131
1132 for id in shutdown_order {
1133 if let Some(entry) = self.tasks.get(id) {
1134 let name = entry.task.name();
1135 let timeout = entry.task.shutdown_timeout();
1136 info!(task_id = %id, "Signalling task '{}' to stop...", name);
1137
1138 let _ = entry.control_tx.send(ControlMessage::Stop);
1139
1140 if let Some(handle) = self.handles.remove(id) {
1141 tokio::pin!(handle);
1144 match tokio::time::timeout(timeout, &mut handle).await {
1145 Ok(res) => {
1146 match res {
1147 Ok(_supervision_res) => {
1148 info!(task_id = %id, "Task '{}' supervision completed.", name);
1149 match tokio::time::timeout(timeout, entry.task.on_shutdown())
1151 .await
1152 {
1153 Ok(_) => {
1154 info!(task_id = %id, "Task '{}' on_shutdown completed.", name)
1155 }
1156 Err(_) => {
1157 warn!(task_id = %id, "Task '{}' on_shutdown timed out after {:?}.", name, timeout)
1158 }
1159 }
1160 }
1161 Err(e) => {
1162 error!(task_id = %id, "Task '{}' panicked during shutdown: {:?}", name, e)
1163 }
1164 }
1165 }
1166 Err(_) => {
1167 warn!(task_id = %id, "Task '{}' did not stop within timeout {:?}. Aborting task.", name, timeout);
1168 handle.abort();
1169 }
1170 }
1171 }
1172 }
1173 }
1174
1175 for (_id, handle) in self.handles.drain() {
1177 handle.abort();
1178 }
1179
1180 let _ = self
1181 .event_tx
1182 .send(SupervisorEvent::SupervisorShutdownCompleted);
1183 info!("[Supervisor] All tasks shut down");
1184 }
1185
1186 fn calculate_shutdown_order(&self) -> Vec<&'static str> {
1189 let mut order = Vec::new();
1190 let mut visited = HashSet::new();
1191
1192 let mut dependents: HashMap<&'static str, Vec<&'static str>> = HashMap::new();
1194 for (id, entry) in &self.tasks {
1195 let active_deps = entry.task.active_dependencies();
1196 for dep_id in &active_deps {
1197 dependents.entry(dep_id).or_default().push(*id);
1198 }
1199 }
1200
1201 fn visit(
1203 id: &'static str,
1204 dependents: &HashMap<&'static str, Vec<&'static str>>,
1205 visited: &mut HashSet<&'static str>,
1206 order: &mut Vec<&'static str>,
1207 ) {
1208 if visited.contains(id) {
1209 return;
1210 }
1211
1212 if let Some(deps) = dependents.get(id) {
1214 for dep in deps {
1215 visit(dep, dependents, visited, order);
1216 }
1217 }
1218
1219 visited.insert(id);
1220 order.push(id);
1221 }
1222
1223 for id in self.tasks.keys() {
1224 visit(id, &dependents, &mut visited, &mut order);
1225 }
1226
1227 order
1228 }
1229
1230 pub fn task_count(&self) -> usize {
1232 self.handles.len()
1233 }
1234
1235 fn validate_restart_policy(
1239 _policy: &crate::enums::RestartPolicy,
1240 ) -> Result<(), SupervisorError> {
1241 Ok(())
1246 }
1247
1248 fn validate_backoff_strategy(
1250 strategy: &crate::enums::BackoffStrategy,
1251 ) -> Result<(), SupervisorError> {
1252 match strategy {
1254 crate::enums::BackoffStrategy::Fixed(duration) => {
1255 if duration.as_secs() > 3600 {
1256 return Err(SupervisorError::config(
1257 "global",
1258 "Fixed backoff delay cannot exceed 1 hour",
1259 ));
1260 }
1261 }
1262 crate::enums::BackoffStrategy::Exponential { initial, max } => {
1263 if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1264 return Err(SupervisorError::config(
1265 "global",
1266 "Exponential backoff delays cannot exceed 1 hour",
1267 ));
1268 }
1269 if initial > max {
1270 return Err(SupervisorError::config(
1271 "global",
1272 "Initial backoff cannot be greater than max backoff",
1273 ));
1274 }
1275 }
1276 crate::enums::BackoffStrategy::Linear {
1277 initial,
1278 increment: _,
1279 max,
1280 } => {
1281 if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1282 return Err(SupervisorError::config(
1283 "global",
1284 "Linear backoff delays cannot exceed 1 hour",
1285 ));
1286 }
1287 if initial > max {
1288 return Err(SupervisorError::config(
1289 "global",
1290 "Initial backoff cannot be greater than max backoff",
1291 ));
1292 }
1293 }
1294 crate::enums::BackoffStrategy::Fibonacci { initial, max } => {
1295 if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1296 return Err(SupervisorError::config(
1297 "global",
1298 "Fibonacci backoff delays cannot exceed 1 hour",
1299 ));
1300 }
1301 if initial > max {
1302 return Err(SupervisorError::config(
1303 "global",
1304 "Initial backoff cannot be greater than max backoff",
1305 ));
1306 }
1307 }
1308 crate::enums::BackoffStrategy::Custom(_) => {
1309 }
1312 }
1313 Ok(())
1314 }
1315}
1316
1317#[derive(Debug, Clone)]
1319pub struct TaskSummary {
1320 pub id: String,
1321 pub name: String,
1322 pub health: HealthStatus,
1323}
1324
1325impl Default for TaskRuntime {
1326 fn default() -> Self {
1327 Self::new()
1328 }
1329}