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