Skip to main content

foxtive_supervisor/runtime/
core.rs

1//! Core TaskRuntime implementation
2//!
3//! This module contains the [`TaskRuntime`] struct, which is the central component
4//! for managing and orchestrating supervised tasks. It handles task registration,
5//! dependency resolution, prerequisite execution, and the spawning of supervision loops.
6
7use super::supervision::{supervise, SupervisionParams};
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;
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::{broadcast, watch, RwLock, Semaphore};
20use tokio::task::JoinHandle;
21use tracing::{error, info, warn};
22
23#[cfg(feature = "cron")]
24use foxtive_cron::Cron;
25
26/// The core runtime for managing supervised tasks.
27///
28/// `TaskRuntime` is responsible for:
29/// - Registering tasks and their configurations.
30/// - Managing task lifecycle (start, stop, pause, resume, restart).
31/// - Handling global and per-task concurrency limits.
32/// - Orchestrating task dependencies and prerequisites.
33/// - Distributing supervisor events to registered listeners.
34/// - Persisting and restoring task states.
35///
36/// It is typically created by the [`crate::Supervisor`] builder and then
37/// used to interact with the running supervised system.
38pub struct TaskRuntime {
39    pub(super) tasks: HashMap<&'static str, TaskEntry>,
40    pub(super) handles: HashMap<&'static str, JoinHandle<SupervisionResult>>,
41    /// Named async gates that must resolve before ANY task starts
42    pub(super) prerequisites: Vec<(&'static str, PrerequisiteFuture)>,
43    /// Build lookup: task_id -> setup watch sender so dependents can subscribe
44    pub(super) setup_signals: HashMap<&'static str, watch::Sender<Option<Result<(), String>>>>,
45    /// Global event broadcaster
46    pub(super) event_tx: broadcast::Sender<SupervisorEvent>,
47    /// Registered event listeners
48    pub(super) listeners: Vec<Arc<dyn SupervisorEventListener>>,
49    /// Optional persistence store for task state
50    pub(super) state_store: Option<Arc<dyn TaskStateStore>>,
51    /// Global concurrency limit
52    pub(super) global_concurrency_limit: Option<Arc<Semaphore>>,
53    /// Per-task concurrency limits
54    pub(crate) task_concurrency_limits: HashMap<&'static str, Arc<Semaphore>>,
55    /// Hot-reloadable task configurations
56    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}
61
62impl fmt::Debug for TaskRuntime {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_struct("TaskRuntime")
65            .field("tasks_count", &self.tasks.len())
66            .field("handles_count", &self.handles.len())
67            .field("prerequisites_count", &self.prerequisites.len())
68            .finish()
69    }
70}
71
72impl TaskRuntime {
73    /// Creates a new, empty `TaskRuntime`.
74    pub fn new() -> Self {
75        let (event_tx, _) = broadcast::channel(1024);
76        #[allow(unused_mut)]
77        let mut runtime = Self {
78            tasks: HashMap::new(),
79            handles: HashMap::new(),
80            prerequisites: Vec::new(),
81            setup_signals: HashMap::new(),
82            event_tx,
83            listeners: Vec::new(),
84            state_store: None,
85            global_concurrency_limit: None,
86            task_concurrency_limits: HashMap::new(),
87            task_configs: HashMap::new(),
88            #[cfg(feature = "cron")]
89            cron: None,
90        };
91        runtime
92    }
93
94    // CONCURRENCY CONTROL
95
96    /// Sets a global concurrency limit for the supervisor.
97    ///
98    /// This limit applies to all tasks and restricts how many can be actively
99    /// running their `run()` method at any given time.
100    pub fn with_global_concurrency_limit(&mut self, limit: usize) -> &mut Self {
101        self.global_concurrency_limit = Some(Arc::new(Semaphore::new(limit)));
102        self
103    }
104
105    // TASK REGISTRATION
106
107    /// Registers a task for supervision.
108    ///
109    /// This method adds the task to the runtime's internal registry but does not
110    /// start its execution. Tasks are started when `start_all()` is called.
111    pub fn register<T: SupervisedTask + 'static>(&mut self, task: T) -> &mut Self {
112        let (setup_tx, _) = watch::channel(None);
113        let (control_tx, _) = broadcast::channel(10);
114        let id = task.id();
115
116        // Setup per-task concurrency limit if specified
117        if let Some(limit) = task.concurrency_limit() {
118            self.task_concurrency_limits.insert(id, Arc::new(Semaphore::new(limit)));
119        }
120
121        // Initialize hot-reloadable config from task's current configuration
122        let config = TaskConfig::from_task(&task);
123        self.task_configs.insert(id, Arc::new(RwLock::new(config)));
124
125        self.setup_signals.insert(id, setup_tx.clone());
126        self.tasks.insert(
127            id,
128            TaskEntry {
129                task: Arc::new(task),
130                setup_tx,
131                control_tx,
132            },
133        );
134        // Event will be emitted during start_all() to ensure all listeners are registered first
135        self
136    }
137
138    /// Registers multiple tasks of the same type at once.
139    pub fn register_many<T: SupervisedTask + 'static>(&mut self, tasks: Vec<T>) -> &mut Self {
140        for task in tasks {
141            self.register(task);
142        }
143        self
144    }
145
146    /// Registers a task from a boxed trait object.
147    ///
148    /// Useful when managing a heterogeneous collection of tasks.
149    pub fn register_boxed(&mut self, task: Box<dyn SupervisedTask>) -> &mut Self {
150        let (setup_tx, _) = watch::channel(None);
151        let (control_tx, _) = broadcast::channel(10);
152        let id = task.id();
153
154        if let Some(limit) = task.concurrency_limit() {
155            self.task_concurrency_limits.insert(id, Arc::new(Semaphore::new(limit)));
156        }
157
158        // Initialize hot-reloadable config from task's current configuration
159        let config = TaskConfig::from_task(task.as_ref());
160        self.task_configs.insert(id, Arc::new(RwLock::new(config)));
161
162        self.setup_signals.insert(id, setup_tx.clone());
163        self.tasks.insert(
164            id,
165            TaskEntry {
166                task: Arc::from(task),
167                setup_tx,
168                control_tx,
169            },
170        );
171        // Event will be emitted during start_all() for consistency with register()
172        self
173    }
174
175    /// Registers a task from an `Arc` (zero extra allocation).
176    ///
177    /// This is the most efficient way to add a task if you already have an `Arc` handle.
178    pub fn register_arc(&mut self, task: Arc<dyn SupervisedTask>) -> &mut Self {
179        let (setup_tx, _) = watch::channel(None);
180        let (control_tx, _) = broadcast::channel(10);
181        let id = task.id();
182
183        if let Some(limit) = task.concurrency_limit() {
184            self.task_concurrency_limits.insert(id, Arc::new(Semaphore::new(limit)));
185        }
186
187        // Initialize hot-reloadable config from task's current configuration
188        let config = TaskConfig::from_task(task.as_ref());
189        self.task_configs.insert(id, Arc::new(RwLock::new(config)));
190
191        self.setup_signals.insert(id, setup_tx.clone());
192        self.tasks.insert(
193            id,
194            TaskEntry {
195                task,
196                setup_tx,
197                control_tx,
198            },
199        );
200        // Event will be emitted during start_all() for consistency with register()
201        self
202    }
203
204    // DYNAMIC TASK MANAGEMENT
205
206    /// Registers and starts a new task at runtime.
207    ///
208    /// This allows adding tasks to an already running supervisor.
209    ///
210    /// # Errors
211    /// Returns `SupervisorError::InternalError` if a task with the same ID already exists.
212    /// Returns `SupervisorError::DependencyValidation` if a declared dependency is unknown.
213    pub fn add_task<T: SupervisedTask + 'static>(&mut self, task: T) -> Result<(), SupervisorError> {
214        let id = task.id();
215        if self.tasks.contains_key(id) {
216            return Err(SupervisorError::InternalError(format!("Task {} already exists", id)));
217        }
218
219        self.register(task);
220
221        // Validate dependency graph including cycle detection for the newly added task
222        let tasks_vec: Vec<&TaskEntry> = self.tasks.values().collect();
223        validate_dependencies(&tasks_vec)?;
224
225        let entry = self.tasks.get(id).unwrap();
226
227        // Collect receivers for each declared dependency
228        let mut dep_receivers = Vec::new();
229        let active_deps = entry.task.active_dependencies();
230        for dep_id in &active_deps {
231            if let Some(sender) = self.setup_signals.get(dep_id) {
232                dep_receivers.push((*dep_id, sender.subscribe()));
233            } else {
234                return Err(SupervisorError::dependency_validation(
235                    id,
236                    dep_id,
237                    crate::error::ValidationError::UnknownTaskId,
238                ));
239            }
240        }
241
242        let task_limit = self.task_concurrency_limits.get(id).cloned();
243        let task_config = self.task_configs.get(id).cloned();
244
245        let params = SupervisionParams {
246            task: entry.task.clone(),
247            setup_tx: entry.setup_tx.clone(),
248            control_rx: entry.control_tx.subscribe(),
249            event_tx: self.event_tx.clone(),
250            dep_receivers,
251            state_store: self.state_store.clone(),
252            global_semaphore: self.global_concurrency_limit.clone(),
253            task_semaphore: task_limit,
254            task_config,
255        };
256
257        let handle = supervise(params);
258        self.handles.insert(id, handle);
259
260        // Send TaskRegistered event for dynamically added task
261        let name = entry.task.name();
262        let _ = self.event_tx.send(SupervisorEvent::TaskRegistered { id: id.to_string(), name });
263
264        Ok(())
265    }
266
267    /// Manually restarts a task by its ID.
268    ///
269    /// This sends a `Restart` control message to the task's supervision loop.
270    ///
271    /// # Errors
272    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
273    pub fn restart_task(&self, id: &str) -> Result<(), SupervisorError> {
274        if let Some(entry) = self.tasks.get(id) {
275            let _ = entry.control_tx.send(ControlMessage::Restart);
276            Ok(())
277        } else {
278            Err(SupervisorError::UnknownTask(id.to_string()))
279        }
280    }
281
282    /// Pauses a task by its ID.
283    ///
284    /// This sends a `Pause` control message to the task's supervision loop.
285    /// A paused task will temporarily stop executing its `run()` method.
286    ///
287    /// # Errors
288    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
289    pub fn pause_task(&self, id: &str) -> Result<(), SupervisorError> {
290        if let Some(entry) = self.tasks.get(id) {
291            let _ = entry.control_tx.send(ControlMessage::Pause);
292            Ok(())
293        } else {
294            Err(SupervisorError::UnknownTask(id.to_string()))
295        }
296    }
297
298    /// Resumes a paused task by its ID.
299    ///
300    /// This sends a `Resume` control message to the task's supervision loop,
301    /// allowing it to continue execution.
302    ///
303    /// # Errors
304    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
305    pub fn resume_task(&self, id: &str) -> Result<(), SupervisorError> {
306        if let Some(entry) = self.tasks.get(id) {
307            let _ = entry.control_tx.send(ControlMessage::Resume);
308            Ok(())
309        } else {
310            Err(SupervisorError::UnknownTask(id.to_string()))
311        }
312    }
313
314    /// Resets the circuit breaker for a task by its ID.
315    ///
316    /// # Errors
317    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
318    pub fn reset_circuit_breaker(&self, id: &str) -> Result<(), SupervisorError> {
319        if let Some(entry) = self.tasks.get(id) {
320            let _ = entry.control_tx.send(ControlMessage::ResetCircuitBreaker);
321            Ok(())
322        } else {
323            Err(SupervisorError::UnknownTask(id.to_string()))
324        }
325    }
326
327    // HOT RELOAD CONFIGURATION
328
329    /// Updates the restart policy for a task at runtime.
330    ///
331    /// The new policy will take effect on the next restart attempt.
332    /// This does not trigger an immediate restart.
333    ///
334    /// # Arguments
335    /// * `id` - The task ID to update
336    /// * `new_policy` - The new restart policy to apply
337    ///
338    /// # Errors
339    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
340    pub async fn update_restart_policy(
341        &self,
342        id: &str,
343        new_policy: crate::enums::RestartPolicy,
344    ) -> Result<(), SupervisorError> {
345        let config_lock = self.task_configs.get(id)
346            .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
347        
348        // Validate the new policy
349        Self::validate_restart_policy(&new_policy)?;
350        
351        let mut config = config_lock.write().await;
352        let old_policy = config.restart_policy.clone();
353        config.restart_policy = new_policy.clone();
354        
355        // Emit configuration change event
356        if let Some(entry) = self.tasks.get(id) {
357            let name = entry.task.name();
358            let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
359                id: id.to_string(),
360                name,
361                field: "restart_policy".to_string(),
362                old_value: format!("{:?}", old_policy),
363                new_value: format!("{:?}", new_policy),
364            });
365        }
366        
367        info!(task_id = %id, ?old_policy, ?new_policy, "Updated restart policy");
368        Ok(())
369    }
370
371    /// Updates the backoff strategy for a task at runtime.
372    ///
373    /// The new strategy will take effect on the next restart attempt.
374    /// This does not trigger an immediate restart.
375    ///
376    /// # Arguments
377    /// * `id` - The task ID to update
378    /// * `new_strategy` - The new backoff strategy to apply
379    ///
380    /// # Errors
381    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
382    pub async fn update_backoff_strategy(
383        &self,
384        id: &str,
385        new_strategy: crate::enums::BackoffStrategy,
386    ) -> Result<(), SupervisorError> {
387        let config_lock = self.task_configs.get(id)
388            .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
389        
390        // Validate the new strategy
391        Self::validate_backoff_strategy(&new_strategy)?;
392        
393        let mut config = config_lock.write().await;
394        let old_strategy = config.backoff_strategy.clone();
395        config.backoff_strategy = new_strategy.clone();
396        
397        // Emit configuration change event
398        if let Some(entry) = self.tasks.get(id) {
399            let name = entry.task.name();
400            let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
401                id: id.to_string(),
402                name,
403                field: "backoff_strategy".to_string(),
404                old_value: format!("{:?}", old_strategy),
405                new_value: format!("{:?}", new_strategy),
406            });
407        }
408        
409        info!(task_id = %id, ?old_strategy, ?new_strategy, "Updated backoff strategy");
410        Ok(())
411    }
412
413    /// Enables or disables a task at runtime.
414    ///
415    /// A disabled task will not restart after failure but will continue running
416    /// if already executing. To stop a running task, use `stop_task()` first.
417    ///
418    /// # Arguments
419    /// * `id` - The task ID to enable/disable
420    /// * `enabled` - Whether the task should be enabled
421    ///
422    /// # Errors
423    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
424    pub async fn set_task_enabled(
425        &self,
426        id: &str,
427        enabled: bool,
428    ) -> Result<(), SupervisorError> {
429        let config_lock = self.task_configs.get(id)
430            .ok_or_else(|| SupervisorError::UnknownTask(id.to_string()))?;
431        
432        let mut config = config_lock.write().await;
433        let old_enabled = config.enabled;
434        config.enabled = enabled;
435        
436        // Emit configuration change event
437        if let Some(entry) = self.tasks.get(id) {
438            let name = entry.task.name();
439            let _ = self.event_tx.send(SupervisorEvent::TaskConfigUpdated {
440                id: id.to_string(),
441                name,
442                field: "enabled".to_string(),
443                old_value: old_enabled.to_string(),
444                new_value: enabled.to_string(),
445            });
446        }
447        
448        info!(task_id = %id, old_enabled = old_enabled, new_enabled = enabled, "Updated task enabled status");
449        Ok(())
450    }
451
452    /// Gets the current configuration for a task.
453    ///
454    /// # Arguments
455    /// * `id` - The task ID to query
456    ///
457    /// # Returns
458    /// A clone of the current task configuration, or None if task not found.
459    pub async fn get_task_config(&self, id: &str) -> Option<TaskConfig> {
460        let config_lock = self.task_configs.get(id)?;
461        let config = config_lock.read().await;
462        Some(config.clone())
463    }
464
465    /// Checks if a task is currently enabled.
466    ///
467    /// # Arguments
468    /// * `id` - The task ID to check
469    ///
470    /// # Returns
471    /// `true` if the task is enabled, `false` otherwise.
472    pub async fn is_task_enabled(&self, id: &str) -> bool {
473        if let Some(config_lock) = self.task_configs.get(id) {
474            let config = config_lock.read().await;
475            config.enabled
476        } else {
477            false
478        }
479    }
480
481    // TASK GROUP MANAGEMENT
482
483    /// Starts all tasks in a specific group.
484    ///
485    /// This is useful for atomic operations where related tasks should be started together.
486    /// If any task in the group fails to start, the operation continues with other tasks.
487    ///
488    /// # Arguments
489    /// * `group_id` - The group identifier to start
490    ///
491    /// # Returns
492    /// Number of tasks started
493    pub fn start_group(&mut self, group_id: &str) -> usize {
494        let mut started_count = 0;
495        
496        // Collect task IDs that belong to this group
497        let group_task_ids: Vec<&'static str> = self.tasks.iter()
498            .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
499            .map(|(id, _)| *id)
500            .collect();
501        
502        for task_id in group_task_ids {
503            if !self.handles.contains_key(task_id) {
504                // Task is registered but not started, start it now
505                if let Err(e) = self.add_task_by_id(task_id) {
506                    error!(task_id = %task_id, error = ?e, "Failed to start task in group");
507                } else {
508                    started_count += 1;
509                }
510            }
511        }
512        
513        info!(group_id = %group_id, started = started_count, "Started task group");
514        started_count
515    }
516
517    /// Stops all tasks in a specific group.
518    ///
519    /// Sends Stop control messages to all tasks in the group and waits for them to terminate.
520    ///
521    /// # Arguments
522    /// * `group_id` - The group identifier to stop
523    ///
524    /// # Returns
525    /// Number of tasks stopped
526    pub async fn stop_group(&mut self, group_id: &str) -> usize {
527        let mut stopped_count = 0;
528        
529        // Collect task IDs that belong to this group
530        let group_task_ids: Vec<&'static str> = self.tasks.iter()
531            .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
532            .map(|(id, _)| *id)
533            .collect();
534        
535        for task_id in group_task_ids {
536            if let Some(handle) = self.handles.remove(task_id) {
537                if let Some(entry) = self.tasks.get(task_id) {
538                    let _ = entry.control_tx.send(ControlMessage::Stop);
539                }
540                
541                // Wait briefly for graceful shutdown
542                match tokio::time::timeout(Duration::from_secs(5), handle).await {
543                    Ok(_) => stopped_count += 1,
544                    Err(_) => {
545                        warn!(task_id = %task_id, "Task in group did not stop gracefully, aborting");
546                        stopped_count += 1; // Count it anyway
547                    }
548                }
549            }
550        }
551        
552        info!(group_id = %group_id, stopped = stopped_count, "Stopped task group");
553        stopped_count
554    }
555
556    /// Restarts all tasks in a specific group.
557    ///
558    /// Sends Restart control messages to all running tasks in the group.
559    ///
560    /// # Arguments
561    /// * `group_id` - The group identifier to restart
562    ///
563    /// # Returns
564    /// Number of tasks restarted
565    pub fn restart_group(&self, group_id: &str) -> usize {
566        let mut restarted_count = 0;
567        
568        for (task_id, entry) in &self.tasks {
569            if entry.task.group_id() == Some(group_id) && self.handles.contains_key(task_id) {
570                let _ = entry.control_tx.send(ControlMessage::Restart);
571                restarted_count += 1;
572            }
573        }
574        
575        info!(group_id = %group_id, restarted = restarted_count, "Restarted task group");
576        restarted_count
577    }
578
579    /// Lists all task IDs in a specific group.
580    ///
581    /// # Arguments
582    /// * `group_id` - The group identifier to query
583    ///
584    /// # Returns
585    /// Vector of task IDs belonging to the group
586    pub fn list_group_tasks(&self, group_id: &str) -> Vec<String> {
587        self.tasks.iter()
588            .filter(|(_, entry)| entry.task.group_id() == Some(group_id))
589            .map(|(id, _)| id.to_string())
590            .collect()
591    }
592
593    /// Gets the aggregated health status for a task group.
594    ///
595    /// The group health is determined by the worst health status among all tasks in the group:
596    /// - If any task is Unhealthy, the group is Unhealthy
597    /// - Else if any task is Degraded, the group is Degraded
598    /// - Else if all tasks are Healthy, the group is Healthy
599    /// - If no tasks are in the group, returns Unknown
600    ///
601    /// # Arguments
602    /// * `group_id` - The group identifier to query
603    ///
604    /// # Returns
605    /// Aggregated HealthStatus for the group
606    pub async fn get_group_health(&self, group_id: &str) -> HealthStatus {
607        let mut has_healthy = false;
608        let mut has_degraded = false;
609        let mut has_unhealthy = false;
610        let mut task_count = 0;
611
612        for entry in self.tasks.values() {
613            if entry.task.group_id() == Some(group_id) {
614                task_count += 1;
615                let health = entry.task.health_check().await;
616                match health {
617                    HealthStatus::Healthy => has_healthy = true,
618                    HealthStatus::Degraded { .. } => has_degraded = true,
619                    HealthStatus::Unhealthy { .. } => has_unhealthy = true,
620                    HealthStatus::Unknown => {}, // Don't affect aggregation
621                }
622            }
623        }
624
625        if task_count == 0 {
626            return HealthStatus::Unknown;
627        }
628
629        // Return worst status
630        if has_unhealthy {
631            HealthStatus::Unhealthy { reason: "One or more tasks in group are unhealthy".to_string() }
632        } else if has_degraded {
633            HealthStatus::Degraded { reason: "One or more tasks in group are degraded".to_string() }
634        } else if has_healthy {
635            HealthStatus::Healthy
636        } else {
637            HealthStatus::Unknown
638        }
639    }
640
641    /// Gets detailed health information for all tasks in a group.
642    ///
643    /// # Arguments
644    /// * `group_id` - The group identifier to query
645    ///
646    /// # Returns
647    /// Vector of TaskSummary for each task in the group
648    pub async fn get_group_health_details(&self, group_id: &str) -> Vec<TaskSummary> {
649        let mut summaries = Vec::new();
650        
651        for (task_id, entry) in &self.tasks {
652            if entry.task.group_id() == Some(group_id) {
653                summaries.push(TaskSummary {
654                    id: task_id.to_string(),
655                    name: entry.task.name(),
656                    health: entry.task.health_check().await,
657                });
658            }
659        }
660        
661        summaries
662    }
663
664    /// Helper method to start a task by ID (used internally for group operations)
665    fn add_task_by_id(&mut self, task_id: &'static str) -> Result<(), SupervisorError> {
666        let entry = self.tasks.get(task_id).unwrap();
667
668        // Collect receivers for each declared dependency
669        let mut dep_receivers = Vec::new();
670        let active_deps = entry.task.active_dependencies();
671        for dep_id in &active_deps {
672            if let Some(sender) = self.setup_signals.get(dep_id) {
673                dep_receivers.push((*dep_id, sender.subscribe()));
674            } else {
675                return Err(SupervisorError::dependency_validation(
676                    task_id,
677                    dep_id,
678                    crate::error::ValidationError::UnknownTaskId,
679                ));
680            }
681        }
682
683        let task_limit = self.task_concurrency_limits.get(task_id).cloned();
684        let task_config = self.task_configs.get(task_id).cloned();
685
686        let params = SupervisionParams {
687            task: entry.task.clone(),
688            setup_tx: entry.setup_tx.clone(),
689            control_rx: entry.control_tx.subscribe(),
690            event_tx: self.event_tx.clone(),
691            dep_receivers,
692            state_store: self.state_store.clone(),
693            global_semaphore: self.global_concurrency_limit.clone(),
694            task_semaphore: task_limit,
695            task_config,
696        };
697
698        let handle = supervise(params);
699        self.handles.insert(task_id, handle);
700
701        let name = entry.task.name();
702        let _ = self.event_tx.send(SupervisorEvent::TaskRegistered { id: task_id.to_string(), name });
703
704        Ok(())
705    }
706
707    /// Stops and removes a task by its ID.
708    ///
709    /// This sends a `Stop` control message to the task, waits for it to terminate,
710    /// and then removes it from the runtime.
711    ///
712    /// # Errors
713    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
714    /// Returns `SupervisorError::InternalError` if the task panicked during removal.
715    pub async fn remove_task(&mut self, id: &str) -> Result<Option<SupervisionResult>, SupervisorError> {
716        if let Some(entry) = self.tasks.remove(id) {
717            let _ = entry.control_tx.send(ControlMessage::Stop);
718            let name = entry.task.name();
719            self.setup_signals.remove(id);
720            let _ = self.event_tx.send(SupervisorEvent::TaskRemoved { id: id.to_string(), name });
721            if let Some(handle) = self.handles.remove(id) {
722                match handle.await {
723                    Ok(res) => Ok(Some(res)),
724                    Err(_) => Err(SupervisorError::InternalError(format!("Task {} panicked during removal", id))),
725                }
726            } else {
727                Ok(None)
728            }
729        } else {
730            Err(SupervisorError::UnknownTask(id.to_string()))
731        }
732    }
733
734    /// Retrieves detailed information about a specific task.
735    ///
736    /// This includes its ID, name, and current health status.
737    ///
738    /// # Errors
739    /// Returns `SupervisorError::UnknownTask` if no task with the given ID is found.
740    pub async fn get_task_info(&self, id: &str) -> Result<TaskSummary, SupervisorError> {
741        if let Some(entry) = self.tasks.get(id) {
742            let health = entry.task.health_check().await;
743            Ok(TaskSummary {
744                id: id.to_string(),
745                name: entry.task.name(),
746                health,
747            })
748        } else {
749            Err(SupervisorError::UnknownTask(id.to_string()))
750        }
751    }
752
753    /// Lists summaries of all currently registered tasks.
754    pub async fn list_tasks(&self) -> Vec<TaskSummary> {
755        let mut summaries = Vec::new();
756        for (id, entry) in &self.tasks {
757            summaries.push(TaskSummary {
758                id: id.to_string(),
759                name: entry.task.name(),
760                health: entry.task.health_check().await,
761            });
762        }
763        summaries
764    }
765
766    // PERSISTENCE
767
768    /// Sets a custom state store for persisting task states.
769    ///
770    /// This method is typically used by the [`crate::Supervisor`] builder.
771    pub fn with_state_store(&mut self, store: Arc<dyn TaskStateStore>) -> &mut Self {
772        self.state_store = Some(store);
773        self
774    }
775
776    // EVENT SYSTEM
777
778    /// Subscribes to the supervisor's event stream.
779    ///
780    /// Returns a `tokio::sync::broadcast::Receiver` that will receive all
781    /// [`SupervisorEvent`]s emitted by the runtime.
782    pub fn subscribe(&self) -> broadcast::Receiver<SupervisorEvent> {
783        self.event_tx.subscribe()
784    }
785
786    /// Registers an event listener.
787    ///
788    /// This method is typically used by the [`crate::Supervisor`] builder.
789    pub fn add_listener(&mut self, listener: Arc<dyn SupervisorEventListener>) -> &mut Self {
790        self.listeners.push(listener);
791        self
792    }
793
794    // PREREQUISITE REGISTRATION
795
796    /// Adds an asynchronous prerequisite gate that must resolve before any task starts.
797    ///
798    /// This method is typically used by the [`crate::Supervisor`] builder.
799    pub fn add_prerequisite(&mut self, name: &'static str, fut: PrerequisiteFuture) -> &mut Self {
800        self.prerequisites.push((name, fut));
801        self
802    }
803
804    /// Adds an asynchronous prerequisite gate using a closure.
805    ///
806    /// This method is typically used by the [`crate::Supervisor`] builder.
807    pub fn add_prerequisite_fn<F, Fut>(&mut self, name: &'static str, f: F) -> &mut Self
808    where
809        F: FnOnce() -> Fut + Send + 'static,
810        Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
811    {
812        self.add_prerequisite(name, Box::pin(async move { f().await }))
813    }
814
815    // STARTUP
816
817    /// Starts all registered tasks.
818    ///
819    /// This method first runs all prerequisites, then validates dependencies,
820    /// and finally spawns the supervision loop for each task.
821    ///
822    /// # Errors
823    /// Returns [`SupervisorError`] if any prerequisite fails or if the dependency graph is invalid.
824    pub async fn start_all(&mut self) -> Result<(), SupervisorError> {
825        // Start event listener distribution
826        let mut event_rx = self.event_tx.subscribe();
827        let listeners = self.listeners.clone();
828        tokio::spawn(async move {
829            loop {
830                match event_rx.recv().await {
831                    Ok(event) => {
832                        for listener in &listeners {
833                            listener.on_event(event.clone()).await;
834                        }
835                    }
836                    Err(broadcast::error::RecvError::Lagged(n)) => {
837                        warn!(missed_events = n, "Event channel lagged, skipping missed events to prevent listener backlog");
838                        // Continue listening for new events instead of terminating
839                        continue;
840                    }
841                    Err(broadcast::error::RecvError::Closed) => {
842                        info!("Event channel closed, stopping event distribution");
843                        break;
844                    }
845                }
846            }
847        });
848
849        // --- Phase 1: prerequisites (run in parallel) ---
850        if !self.prerequisites.is_empty() {
851            info!("[Supervisor] Running {} prerequisites in parallel...", self.prerequisites.len());
852            
853            let prereq_futures: Vec<_> = self.prerequisites.drain(..)
854                .map(|(name, fut)| async move {
855                    info!("[Supervisor] Awaiting prerequisite '{name}'...");
856                    fut.await.map_err(|e| (name, e))
857                })
858                .collect();
859            
860            let results = futures_util::future::try_join_all(prereq_futures).await
861                .map_err(|(name, e)| SupervisorError::prerequisite_failed(name, e))?;
862            
863            info!("[Supervisor] All {} prerequisites satisfied", results.len());
864        }
865
866        if self.tasks.is_empty() {
867            warn!("[Supervisor] No tasks registered");
868            return Ok(());
869        }
870
871        // --- Phase 2: validate dependency graph ---
872        let tasks_vec: Vec<&TaskEntry> = self.tasks.values().collect();
873        validate_dependencies(&tasks_vec)?;
874
875        info!(
876            "[Supervisor] Starting {} supervised tasks...",
877            self.tasks.len()
878        );
879
880        // Sort tasks by priority (highest first) for startup order
881        let mut sorted_ids: Vec<&'static str> = self.tasks.keys().copied().collect();
882        sorted_ids.sort_by(|a, b| {
883            let task_a = &self.tasks[a].task;
884            let task_b = &self.tasks[b].task;
885            task_b.priority().cmp(&task_a.priority())
886        });
887
888        // --- Phase 3: spawn ---
889        for id in sorted_ids {
890            let entry = &self.tasks[id];
891            // Emit TaskRegistered event during startup (all listeners are registered by now)
892            let name = entry.task.name();
893            let _ = self.event_tx.send(SupervisorEvent::TaskRegistered { id: id.to_string(), name });
894
895            // Collect receivers for each declared dependency
896            let active_deps = entry.task.active_dependencies();
897            let dep_receivers: DepSetupReceivers = active_deps
898                .iter()
899                .map(|dep_id| {
900                    let sender = self.setup_signals[dep_id].clone();
901                    (*dep_id, sender.subscribe())
902                })
903                .collect();
904
905            let task_limit = self.task_concurrency_limits.get(id).cloned();
906            let task_config = self.task_configs.get(id).cloned();
907
908            let params = SupervisionParams {
909                task: entry.task.clone(),
910                setup_tx: entry.setup_tx.clone(),
911                control_rx: entry.control_tx.subscribe(),
912                event_tx: self.event_tx.clone(),
913                dep_receivers,
914                state_store: self.state_store.clone(),
915                global_semaphore: self.global_concurrency_limit.clone(),
916                task_semaphore: task_limit,
917                task_config,
918            };
919
920            let handle = supervise(params);
921            self.handles.insert(id, handle);
922        }
923
924        info!("[Supervisor] All tasks started");
925        Ok(())
926    }
927
928    /// Starts a single task with no dependencies (fire and forget).
929    ///
930    /// This is a convenience function for simple, isolated task supervision
931    /// without needing a full `TaskRuntime` setup.
932    pub fn start_one<T: SupervisedTask + 'static>(task: T) -> JoinHandle<SupervisionResult> {
933        let (setup_tx, _) = watch::channel(None);
934        let (control_tx, _) = broadcast::channel(10);
935        let (event_tx, _) = broadcast::channel(1);
936        let params = SupervisionParams {
937            task: Arc::new(task),
938            setup_tx,
939            control_rx: control_tx.subscribe(),
940            event_tx,
941            dep_receivers: vec![],
942            state_store: None,
943            global_semaphore: None,
944            task_semaphore: None,
945            task_config: None,
946        };
947        supervise(params)
948    }
949
950    // WAITING
951
952    /// Waits for any one supervised task to terminate.
953    ///
954    /// Returns the `SupervisionResult` of the first task that finishes.
955    pub async fn wait_any(&mut self) -> SupervisionResult {
956        if self.handles.is_empty() {
957            warn!("[Supervisor] No tasks to wait for");
958            return SupervisionResult {
959                task_name: "none".to_string(),
960                task_id: "none".to_string(),
961                total_attempts: 0,
962                final_status: crate::enums::SupervisionStatus::ManuallyStopped,
963            };
964        }
965
966        // Extract all handles from the HashMap to avoid borrow issues
967        let mut handle_vec: Vec<(&'static str, JoinHandle<SupervisionResult>)> = self.handles.drain().collect();
968        
969        // Use select_all to wait for the first one to complete
970        use futures_util::future::select_all;
971        
972        let mut futures = Vec::new();
973        for (id, handle) in handle_vec.iter_mut() {
974            let id_clone = *id;
975            futures.push(Box::pin(async move {
976                let result = handle.await;
977                (id_clone, result)
978            }));
979        }
980        
981        let ((finished_id, result), _idx, _remaining) = select_all(futures).await;
982        
983        // Put remaining handles back into the HashMap
984        for (i, (id, handle)) in handle_vec.into_iter().enumerate() {
985            if i != _idx {
986                self.handles.insert(id, handle);
987            }
988        }
989        
990        match result {
991            Ok(supervision_result) => {
992                error!(
993                    "[Supervisor] Task '{}' (id: {}) terminated: {:?}",
994                    supervision_result.task_name,
995                    supervision_result.task_id,
996                    supervision_result.final_status
997                );
998                supervision_result
999            }
1000            Err(join_err) => {
1001                error!(
1002                    "[Supervisor] Task {} panicked: {:?}",
1003                    finished_id, join_err
1004                );
1005                SupervisionResult {
1006                    task_name: "unknown".to_string(),
1007                    task_id: finished_id.to_string(),
1008                    total_attempts: 0,
1009                    final_status: crate::enums::SupervisionStatus::ManuallyStopped,
1010                }
1011            }
1012        }
1013    }
1014
1015    /// Waits for all supervised tasks to terminate.
1016    ///
1017    /// Returns a vector of `SupervisionResult` for all tasks.
1018    pub async fn wait_all(&mut self) -> Vec<SupervisionResult> {
1019        let mut results = Vec::new();
1020        while !self.handles.is_empty() {
1021            results.push(self.wait_any().await);
1022        }
1023        results
1024    }
1025
1026    /// Initiates a graceful shutdown of all supervised tasks.
1027    ///
1028    /// This sends a `Stop` control message to each task and waits for them
1029    /// to complete their `on_shutdown()` hooks and terminate.
1030    ///
1031    /// Shutdown is performed in reverse dependency order (leaves first, then roots).
1032    pub async fn shutdown(mut self) {
1033        info!("[Supervisor] Shutting down {} tasks...", self.tasks.len());
1034        let _ = self.event_tx.send(SupervisorEvent::SupervisorShutdownStarted);
1035
1036        // Calculate shutdown order based on dependencies.
1037        // We want to shut down tasks that NO OTHER task depends on first.
1038        let shutdown_order = self.calculate_shutdown_order();
1039
1040        for id in shutdown_order {
1041            if let Some(entry) = self.tasks.get(id) {
1042                let name = entry.task.name();
1043                let timeout = entry.task.shutdown_timeout();
1044                info!(task_id = %id, "Signalling task '{}' to stop...", name);
1045
1046                let _ = entry.control_tx.send(ControlMessage::Stop);
1047
1048                if let Some(handle) = self.handles.remove(id) {
1049                    // Wait for the supervision loop to finish (it will abort run_handle and call cleanup)
1050                    match tokio::time::timeout(timeout, handle).await {
1051                        Ok(res) => {
1052                            match res {
1053                                Ok(_supervision_res) => {
1054                                    info!(task_id = %id, "Task '{}' supervision completed.", name);
1055                                    // Call on_shutdown hook - separate from cleanup(), this runs once during graceful shutdown
1056                                    match tokio::time::timeout(timeout, entry.task.on_shutdown()).await {
1057                                        Ok(_) => info!(task_id = %id, "Task '{}' on_shutdown completed.", name),
1058                                        Err(_) => warn!(task_id = %id, "Task '{}' on_shutdown timed out after {:?}.", name, timeout),
1059                                    }
1060                                }
1061                                Err(e) => error!(task_id = %id, "Task '{}' panicked during shutdown: {:?}", name, e),
1062                            }
1063                        }
1064                        Err(_) => {
1065                            warn!(task_id = %id, "Task '{}' did not stop within timeout {:?}. Task will continue in background.", name, timeout);
1066                            // Note: handle was consumed by timeout(), so we can't abort it here.
1067                            // The task will continue running but shutdown will proceed.
1068                        }
1069                    }
1070                }
1071            }
1072        }
1073
1074        // Clean up any remaining handles (tasks that weren't in the dependency graph somehow)
1075        for (_id, handle) in self.handles.drain() {
1076            handle.abort();
1077        }
1078
1079        let _ = self.event_tx.send(SupervisorEvent::SupervisorShutdownCompleted);
1080        info!("[Supervisor] All tasks shut down");
1081    }
1082
1083    /// Calculates the order in which tasks should be shut down.
1084    /// Tasks with no dependents are shut down first.
1085    fn calculate_shutdown_order(&self) -> Vec<&'static str> {
1086        let mut order = Vec::new();
1087        let mut visited = HashSet::new();
1088
1089        // Build adjacency list for "is depended on by"
1090        let mut dependents: HashMap<&'static str, Vec<&'static str>> = HashMap::new();
1091        for (id, entry) in &self.tasks {
1092            let active_deps = entry.task.active_dependencies();
1093            for dep_id in &active_deps {
1094                dependents.entry(dep_id).or_default().push(*id);
1095            }
1096        }
1097
1098        // Helper for DFS to find shutdown order
1099        fn visit(
1100            id: &'static str,
1101            dependents: &HashMap<&'static str, Vec<&'static str>>,
1102            visited: &mut HashSet<&'static str>,
1103            order: &mut Vec<&'static str>,
1104        ) {
1105            if visited.contains(id) {
1106                return;
1107            }
1108
1109            // Visit all tasks that depend on this task first
1110            if let Some(deps) = dependents.get(id) {
1111                for dep in deps {
1112                    visit(dep, dependents, visited, order);
1113                }
1114            }
1115
1116            visited.insert(id);
1117            order.push(id);
1118        }
1119
1120        for id in self.tasks.keys() {
1121            visit(id, &dependents, &mut visited, &mut order);
1122        }
1123
1124        order
1125    }
1126
1127    /// Returns the number of tasks currently being supervised.
1128    pub fn task_count(&self) -> usize {
1129        self.handles.len()
1130    }
1131
1132    // CONFIGURATION VALIDATION
1133
1134    /// Validates a restart policy before applying it
1135    fn validate_restart_policy(_policy: &crate::enums::RestartPolicy) -> Result<(), SupervisorError> {
1136        // Currently all restart policies are valid
1137        // Future enhancements could add constraints like:
1138        // - MaxAttempts must be > 0
1139        // - Warn if changing from Always to Never on critical tasks
1140        Ok(())
1141    }
1142
1143    /// Validates a backoff strategy before applying it
1144    fn validate_backoff_strategy(strategy: &crate::enums::BackoffStrategy) -> Result<(), SupervisorError> {
1145        // Validate that delays are reasonable (not too short, not too long)
1146        match strategy {
1147            crate::enums::BackoffStrategy::Fixed(duration) => {
1148                if duration.as_secs() > 3600 {
1149                    return Err(SupervisorError::InternalError(
1150                        "Fixed backoff delay cannot exceed 1 hour".to_string()
1151                    ));
1152                }
1153            }
1154            crate::enums::BackoffStrategy::Exponential { initial, max } => {
1155                if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1156                    return Err(SupervisorError::InternalError(
1157                        "Exponential backoff delays cannot exceed 1 hour".to_string()
1158                    ));
1159                }
1160                if initial > max {
1161                    return Err(SupervisorError::InternalError(
1162                        "Initial backoff cannot be greater than max backoff".to_string()
1163                    ));
1164                }
1165            }
1166            crate::enums::BackoffStrategy::Linear { initial, increment: _, max } => {
1167                if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1168                    return Err(SupervisorError::InternalError(
1169                        "Linear backoff delays cannot exceed 1 hour".to_string()
1170                    ));
1171                }
1172                if initial > max {
1173                    return Err(SupervisorError::InternalError(
1174                        "Initial backoff cannot be greater than max backoff".to_string()
1175                    ));
1176                }
1177            }
1178            crate::enums::BackoffStrategy::Fibonacci { initial, max } => {
1179                if initial.as_secs() > 3600 || max.as_secs() > 3600 {
1180                    return Err(SupervisorError::InternalError(
1181                        "Fibonacci backoff delays cannot exceed 1 hour".to_string()
1182                    ));
1183                }
1184                if initial > max {
1185                    return Err(SupervisorError::InternalError(
1186                        "Initial backoff cannot be greater than max backoff".to_string()
1187                    ));
1188                }
1189            }
1190            crate::enums::BackoffStrategy::Custom(_) => {
1191                // Custom strategies can't be validated without executing them
1192                // Trust the user's implementation
1193            }
1194        }
1195        Ok(())
1196    }
1197}
1198
1199/// A summary of a task's current status.
1200#[derive(Debug, Clone)]
1201pub struct TaskSummary {
1202    pub id: String,
1203    pub name: String,
1204    pub health: HealthStatus,
1205}
1206
1207impl Default for TaskRuntime {
1208    fn default() -> Self {
1209        Self::new()
1210    }
1211}