flare_core_runtime/task/
manager.rs1use super::{Task, TaskResult, TaskState};
6use crate::config::RuntimeConfig;
7use crate::error::RuntimeError;
8use crate::state::StateTracker;
9use crate::utils::topological_sort;
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::sync::oneshot;
14use tokio::task::JoinSet;
15use tracing::{debug, error, info, warn};
16
17pub struct TaskManager {
49 tasks: Vec<Box<dyn Task>>,
51 state_tracker: Arc<StateTracker>,
53 config: RuntimeConfig,
55}
56
57impl TaskManager {
58 pub fn new() -> Self {
60 Self {
61 tasks: Vec::new(),
62 state_tracker: Arc::new(StateTracker::new()),
63 config: RuntimeConfig::default(),
64 }
65 }
66
67 pub fn with_config(config: RuntimeConfig) -> Self {
69 Self {
70 tasks: Vec::new(),
71 state_tracker: Arc::new(StateTracker::new()),
72 config,
73 }
74 }
75
76 pub fn add_task(&mut self, task: Box<dyn Task>) {
78 debug!(task_name = %task.name(), "Adding task to manager");
79 self.tasks.push(task);
80 }
81
82 pub fn task_count(&self) -> usize {
84 self.tasks.len()
85 }
86
87 pub fn state_tracker(&self) -> Arc<StateTracker> {
89 Arc::clone(&self.state_tracker)
90 }
91
92 pub async fn start_all(
104 &mut self,
105 ) -> Result<(JoinSet<TaskResult>, Vec<oneshot::Sender<()>>), RuntimeError> {
106 info!(task_count = self.tasks.len(), "Starting all tasks");
107
108 let sorted_tasks = self.sort_tasks()?;
110
111 for task in &sorted_tasks {
113 self.state_tracker
114 .register_task(task.name(), TaskState::Pending)
115 .await;
116 }
117
118 let mut join_set = JoinSet::new();
120 let mut shutdown_txs = Vec::new();
121
122 for task in sorted_tasks {
123 let task_name = task.name().to_string();
124 let (shutdown_tx, shutdown_rx) = oneshot::channel();
125 shutdown_txs.push(shutdown_tx);
126
127 self.state_tracker
129 .update_state(&task_name, TaskState::Starting)
130 .await;
131
132 let state_tracker = Arc::clone(&self.state_tracker);
134 let task_future = task.run(shutdown_rx);
135
136 join_set.spawn(async move {
137 state_tracker
139 .update_state(&task_name, TaskState::Running)
140 .await;
141
142 let result = task_future.await;
144
145 match &result {
147 Ok(_) => {
148 debug!(task_name = %task_name, "Task completed");
149 state_tracker
150 .update_state(&task_name, TaskState::Stopped)
151 .await;
152 }
153 Err(e) => {
154 error!(task_name = %task_name, error = %e, "❌ Task failed");
155 state_tracker
156 .update_state_with_error(&task_name, TaskState::Failed, e.to_string())
157 .await;
158 }
159 }
160
161 result
162 });
163 }
164
165 info!("All tasks started");
166 Ok((join_set, shutdown_txs))
167 }
168
169 pub async fn stop_all(
176 &self,
177 mut join_set: JoinSet<TaskResult>,
178 shutdown_txs: Vec<oneshot::Sender<()>>,
179 ) {
180 info!("Stopping all tasks");
181
182 for tx in shutdown_txs {
184 let _ = tx.send(());
185 }
186
187 match tokio::time::timeout(self.config.shutdown_timeout, async {
189 while let Some(result) = join_set.join_next().await {
190 match result {
191 Ok(Ok(_)) => {
192 debug!("Task completed gracefully");
193 }
194 Ok(Err(e)) => {
195 warn!("Task completed with error: {}", e);
196 }
197 Err(e) => {
198 warn!("Task join error: {}", e);
199 }
200 }
201 }
202 })
203 .await
204 {
205 Ok(_) => {
206 info!("All tasks completed");
207 }
208 Err(_) => {
209 warn!("Tasks shutdown timeout, forcing exit");
210 join_set.abort_all();
211 }
212 }
213 }
214
215 pub async fn wait_for_ready(&self) -> Result<(), RuntimeError> {
217 if !self.config.task_startup.enable_ready_check {
218 debug!("Task ready check is disabled, skipping");
219 return Ok(());
220 }
221
222 info!("Waiting for all tasks to be ready");
223
224 let timeout = self.config.task_startup.ready_check_timeout;
226 let start = std::time::Instant::now();
227
228 loop {
229 if self.state_tracker.all_ready().await {
230 info!("✅ All tasks are ready");
231 return Ok(());
232 }
233
234 if start.elapsed() > timeout {
235 return Err(RuntimeError::StartupTimeout {
236 name: "all tasks".to_string(),
237 timeout,
238 });
239 }
240
241 tokio::time::sleep(Duration::from_millis(100)).await;
242 }
243 }
244
245 fn sort_tasks(&mut self) -> Result<Vec<Box<dyn Task>>, RuntimeError> {
247 let items: Vec<(String, Vec<String>)> = self
249 .tasks
250 .iter()
251 .map(|task| (task.name().to_string(), task.dependencies()))
252 .collect();
253
254 let sorted_names = topological_sort(items)
256 .map_err(|cycle| RuntimeError::CircularDependency { tasks: cycle })?;
257
258 let mut by_name: HashMap<String, Box<dyn Task>> = HashMap::new();
260 for task in self.tasks.drain(..) {
261 by_name.insert(task.name().to_string(), task);
262 }
263
264 let mut sorted_tasks = Vec::with_capacity(sorted_names.len());
265 for name in sorted_names {
266 if let Some(task) = by_name.remove(&name) {
267 sorted_tasks.push(task);
268 }
269 }
270 for (_, task) in by_name {
271 warn!(
272 task_name = %task.name(),
273 "Task missing from topological order output, appending at end"
274 );
275 sorted_tasks.push(task);
276 }
277
278 Ok(sorted_tasks)
279 }
280}
281
282impl Default for TaskManager {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::task::SpawnTask;
292
293 #[tokio::test]
294 async fn test_task_manager_new() {
295 let manager = TaskManager::new();
296 assert_eq!(manager.task_count(), 0);
297 }
298
299 #[tokio::test]
300 async fn test_task_manager_add_task() {
301 let mut manager = TaskManager::new();
302 manager.add_task(Box::new(SpawnTask::new("task-1", async { Ok(()) })));
303 assert_eq!(manager.task_count(), 1);
304 }
305
306 #[tokio::test]
307 async fn test_task_manager_state_tracker() {
308 let manager = TaskManager::new();
309 let _tracker = manager.state_tracker();
310 }
311
312 #[tokio::test]
314 async fn test_task_manager_start_all_three_independent_no_panic() {
315 let mut manager = TaskManager::new();
316 manager.add_task(Box::new(SpawnTask::new("conversation-grpc", async {
317 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
318 Ok(())
319 })));
320 manager.add_task(Box::new(SpawnTask::new("read-receipt-consumer", async {
321 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
322 Ok(())
323 })));
324 manager.add_task(Box::new(SpawnTask::new(
325 "conversation-ensure-consumer",
326 async {
327 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
328 Ok(())
329 },
330 )));
331
332 let (mut join_set, shutdown_txs) = manager.start_all().await.expect("start_all");
333 for tx in shutdown_txs {
334 let _ = tx.send(());
335 }
336 while join_set.join_next().await.is_some() {}
337 }
338}