1use super::task::{Task, TaskResult};
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex};
5use tokio::task::JoinHandle;
6use uuid::Uuid;
7
8pub struct WorkerPool {
10 workers: Arc<Mutex<HashMap<Uuid, WorkerInfo>>>,
11 max_workers: usize,
12 reporter: Arc<dyn crate::core::report::Reporter>,
13}
14
15#[derive(Debug)]
16struct WorkerInfo {
17 handle: JoinHandle<TaskResult>,
18 task_id: String,
19 start_time: std::time::Instant,
20 worker_type: WorkerType,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum WorkerType {
29 CpuIntensive,
31 IoIntensive,
33 Mixed,
35}
36
37impl WorkerPool {
38 pub fn new(max_workers: usize) -> Self {
47 Self {
48 workers: Arc::new(Mutex::new(HashMap::new())),
49 max_workers,
50 reporter: crate::core::report::noop(),
51 }
52 }
53
54 pub fn with_reporter(mut self, reporter: Arc<dyn crate::core::report::Reporter>) -> Self {
61 self.reporter = reporter;
62 self
63 }
64
65 fn reporter(&self) -> Arc<dyn crate::core::report::Reporter> {
67 Arc::clone(&self.reporter)
68 }
69
70 pub async fn execute(&self, task: Box<dyn Task + Send + Sync>) -> Result<TaskResult, String> {
72 let worker_id = Uuid::now_v7();
73 let task_id = task.task_id();
74 let worker_type = self.determine_worker_type(task.task_type());
75
76 {
77 let workers = self.workers.lock().unwrap();
78 if workers.len() >= self.max_workers {
79 return Err("Worker pool is full".to_string());
80 }
81 }
82
83 let handle = tokio::spawn(async move { task.execute().await });
84
85 {
86 let mut workers = self.workers.lock().unwrap();
87 workers.insert(
88 worker_id,
89 WorkerInfo {
90 handle,
91 task_id: task_id.clone(),
92 start_time: std::time::Instant::now(),
93 worker_type,
94 },
95 );
96 }
97
98 Ok(TaskResult::Success("Task submitted".to_string()))
100 }
101
102 fn determine_worker_type(&self, task_type: &str) -> WorkerType {
103 match task_type {
104 "convert" => WorkerType::CpuIntensive,
105 "sync" => WorkerType::Mixed,
106 "match" => WorkerType::IoIntensive,
107 "validate" => WorkerType::IoIntensive,
108 _ => WorkerType::Mixed,
109 }
110 }
111
112 pub fn get_active_count(&self) -> usize {
114 self.workers.lock().unwrap().len()
115 }
116
117 pub fn get_capacity(&self) -> usize {
119 self.max_workers
120 }
121
122 pub fn get_worker_stats(&self) -> WorkerStats {
124 let workers = self.workers.lock().unwrap();
125 let mut cpu = 0;
126 let mut io = 0;
127 let mut mixed = 0;
128 for w in workers.values() {
129 match w.worker_type {
130 WorkerType::CpuIntensive => cpu += 1,
131 WorkerType::IoIntensive => io += 1,
132 WorkerType::Mixed => mixed += 1,
133 }
134 }
135 WorkerStats {
136 total_active: workers.len(),
137 cpu_intensive_count: cpu,
138 io_intensive_count: io,
139 mixed_count: mixed,
140 max_capacity: self.max_workers,
141 }
142 }
143
144 pub async fn shutdown(&self) {
146 let reporter = self.reporter();
147 let workers = { std::mem::take(&mut *self.workers.lock().unwrap()) };
148 for (id, info) in workers {
149 reporter.progress(&crate::core::report::ProgressEvent::Message(&format!(
152 "Waiting for worker {id} to complete task {}",
153 info.task_id
154 )));
155 let _ = info.handle.await;
156 }
157 }
158
159 pub fn list_active_workers(&self) -> Vec<ActiveWorkerInfo> {
161 let workers = self.workers.lock().unwrap();
162 workers
163 .iter()
164 .map(|(id, info)| ActiveWorkerInfo {
165 worker_id: *id,
166 task_id: info.task_id.clone(),
167 worker_type: info.worker_type.clone(),
168 runtime: info.start_time.elapsed(),
169 })
170 .collect()
171 }
172}
173
174impl Clone for WorkerPool {
175 fn clone(&self) -> Self {
176 Self {
177 workers: Arc::clone(&self.workers),
178 max_workers: self.max_workers,
179 reporter: Arc::clone(&self.reporter),
180 }
181 }
182}
183
184#[derive(Debug, Clone)]
189pub struct WorkerStats {
190 pub total_active: usize,
192 pub cpu_intensive_count: usize,
194 pub io_intensive_count: usize,
196 pub mixed_count: usize,
198 pub max_capacity: usize,
200}
201
202#[derive(Debug, Clone)]
206pub struct ActiveWorkerInfo {
207 pub worker_id: Uuid,
209 pub task_id: String,
211 pub worker_type: WorkerType,
213 pub runtime: std::time::Duration,
215}
216
217pub struct Worker {
219 id: Uuid,
220 status: WorkerStatus,
221}
222
223#[derive(Debug, Clone)]
228pub enum WorkerStatus {
229 Idle,
231 Busy(String),
233 Stopped,
235 Error(String),
237}
238
239impl Worker {
240 pub fn new() -> Self {
242 Self {
243 id: Uuid::now_v7(),
244 status: WorkerStatus::Idle,
245 }
246 }
247
248 pub fn id(&self) -> Uuid {
250 self.id
251 }
252
253 pub fn status(&self) -> &WorkerStatus {
255 &self.status
256 }
257
258 pub fn set_status(&mut self, status: WorkerStatus) {
264 self.status = status;
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[tokio::test]
273 async fn test_worker_pool_capacity() {
274 let pool = WorkerPool::new(2);
275 assert_eq!(pool.get_capacity(), 2);
276 assert_eq!(pool.get_active_count(), 0);
277 let stats = pool.get_worker_stats();
278 assert_eq!(stats.max_capacity, 2);
279 assert_eq!(stats.total_active, 0);
280 }
281
282 #[tokio::test]
283 async fn test_execute_and_active_count() {
284 use crate::core::parallel::task::{Task, TaskResult};
285 use async_trait::async_trait;
286
287 #[derive(Clone)]
288 struct DummyTask {
289 id: String,
290 tp: &'static str,
291 }
292
293 #[async_trait]
294 impl Task for DummyTask {
295 async fn execute(&self) -> TaskResult {
296 TaskResult::Success(self.id.clone())
297 }
298 fn task_type(&self) -> &'static str {
299 self.tp
300 }
301 fn task_id(&self) -> String {
302 self.id.clone()
303 }
304 }
305
306 let pool = WorkerPool::new(1);
307 let task = DummyTask {
308 id: "t1".into(),
309 tp: "convert",
310 };
311 let res = pool.execute(Box::new(task.clone())).await;
312 assert!(matches!(res, Ok(TaskResult::Success(_))));
313 assert_eq!(pool.get_active_count(), 1);
314 }
315
316 #[tokio::test]
317 async fn test_reject_when_full() {
318 use crate::core::parallel::task::{Task, TaskResult};
319 use async_trait::async_trait;
320
321 #[derive(Clone)]
322 struct DummyTask;
323
324 #[async_trait]
325 impl Task for DummyTask {
326 async fn execute(&self) -> TaskResult {
327 TaskResult::Success("".into())
328 }
329 fn task_type(&self) -> &'static str {
330 "match"
331 }
332 fn task_id(&self) -> String {
333 "".into()
334 }
335 }
336
337 let pool = WorkerPool::new(1);
338 let _ = pool.execute(Box::new(DummyTask)).await;
339 let err = pool.execute(Box::new(DummyTask)).await;
340 assert!(err.is_err());
341 }
342
343 #[tokio::test]
344 async fn test_list_active_workers_and_stats() {
345 use super::WorkerType;
346 use crate::core::parallel::task::{Task, TaskResult};
347 use async_trait::async_trait;
348
349 #[derive(Clone)]
350 struct DummyTask2;
351
352 #[async_trait]
353 impl Task for DummyTask2 {
354 async fn execute(&self) -> TaskResult {
355 TaskResult::Success("".into())
356 }
357 fn task_type(&self) -> &'static str {
358 "sync"
359 }
360 fn task_id(&self) -> String {
361 "tok2".into()
362 }
363 }
364
365 let pool = WorkerPool::new(2);
366 let _ = pool.execute(Box::new(DummyTask2)).await;
367 let workers = pool.list_active_workers();
368 assert_eq!(workers.len(), 1);
369 let info = &workers[0];
370 assert_eq!(info.task_id, "tok2");
371 assert_eq!(info.worker_type, WorkerType::Mixed);
372 let stats = pool.get_worker_stats();
373 assert_eq!(stats.total_active, 1);
374 }
375
376 #[tokio::test]
378 async fn test_worker_job_distribution() {
379 use crate::core::parallel::task::{Task, TaskResult};
380 use async_trait::async_trait;
381 use std::sync::Arc;
382 use std::sync::atomic::{AtomicUsize, Ordering};
383
384 #[derive(Clone)]
385 struct CountingTask {
386 id: String,
387 counter: Arc<AtomicUsize>,
388 }
389
390 #[async_trait]
391 impl Task for CountingTask {
392 async fn execute(&self) -> TaskResult {
393 self.counter.fetch_add(1, Ordering::SeqCst);
394 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
395 TaskResult::Success(format!("task-{}", self.id))
396 }
397 fn task_type(&self) -> &'static str {
398 "convert"
399 }
400 fn task_id(&self) -> String {
401 self.id.clone()
402 }
403 }
404
405 let pool = WorkerPool::new(4);
406 let counter = Arc::new(AtomicUsize::new(0));
407 let mut handles = Vec::new();
408
409 for i in 0..4 {
411 let task = CountingTask {
413 id: format!("task-{}", i),
414 counter: Arc::clone(&counter),
415 };
416
417 let pool_clone = pool.clone();
419 let handle = tokio::spawn(async move { pool_clone.execute(Box::new(task)).await });
420 handles.push(handle);
421 }
422
423 for handle in handles {
425 let result = handle.await.unwrap();
426 assert!(result.is_ok(), "Task submission should succeed");
427 }
428
429 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
431
432 let final_count = counter.load(Ordering::SeqCst);
434 assert_eq!(final_count, 4, "All 4 tasks should have been executed");
435 }
436
437 #[tokio::test]
439 async fn test_worker_error_recovery() {
440 use crate::core::parallel::task::{Task, TaskResult};
441 use async_trait::async_trait;
442
443 #[derive(Clone)]
444 struct FailingTask {
445 id: String,
446 should_fail: bool,
447 }
448
449 #[async_trait]
450 impl Task for FailingTask {
451 async fn execute(&self) -> TaskResult {
452 if self.should_fail {
453 TaskResult::Failed("Intentional failure".to_string())
454 } else {
455 TaskResult::Success(format!("success-{}", self.id))
456 }
457 }
458 fn task_type(&self) -> &'static str {
459 "sync"
460 }
461 fn task_id(&self) -> String {
462 self.id.clone()
463 }
464 }
465
466 let pool = WorkerPool::new(2);
467
468 let success_task = FailingTask {
470 id: "success".to_string(),
471 should_fail: false,
472 };
473 let result = pool.execute(Box::new(success_task)).await;
474 assert!(result.is_ok(), "Successful task should be submitted");
475
476 let fail_task = FailingTask {
478 id: "fail".to_string(),
479 should_fail: true,
480 };
481 let result = pool.execute(Box::new(fail_task)).await;
482 assert!(
483 result.is_ok(),
484 "Failing task should still be submitted successfully"
485 );
486
487 assert!(
489 pool.get_active_count() <= 2,
490 "Active count should be within limits"
491 );
492 }
493
494 #[tokio::test]
496 async fn test_parallel_processing_performance() {
497 use crate::core::parallel::task::{Task, TaskResult};
498 use async_trait::async_trait;
499 use std::time::Instant;
500
501 #[derive(Clone)]
502 struct CpuIntensiveTask {
503 id: String,
504 duration_ms: u64,
505 }
506
507 #[async_trait]
508 impl Task for CpuIntensiveTask {
509 async fn execute(&self) -> TaskResult {
510 tokio::time::sleep(tokio::time::Duration::from_millis(self.duration_ms)).await;
512 TaskResult::Success(format!("completed-{}", self.id))
513 }
514 fn task_type(&self) -> &'static str {
515 "convert"
516 }
517 fn task_id(&self) -> String {
518 self.id.clone()
519 }
520 }
521
522 let sequential_pool = WorkerPool::new(1);
524 let start = Instant::now();
525
526 for i in 0..2 {
527 let task = CpuIntensiveTask {
529 id: format!("seq-{}", i),
530 duration_ms: 10, };
532 if let Err(e) = sequential_pool.execute(Box::new(task)).await {
533 println!("Sequential task {} failed: {}", i, e);
534 }
536 }
537 let sequential_time = start.elapsed();
538
539 let parallel_pool = WorkerPool::new(2); let start = Instant::now();
542
543 let task = CpuIntensiveTask {
545 id: "par-0".to_string(),
546 duration_ms: 10,
547 };
548 if let Err(e) = parallel_pool.execute(Box::new(task)).await {
549 println!("Parallel task failed: {}", e);
550 }
551 let parallel_time = start.elapsed();
552
553 println!("Sequential submission time: {:?}", sequential_time);
556 println!("Parallel submission time: {:?}", parallel_time);
557
558 assert!(
560 parallel_time <= sequential_time * 2,
561 "Parallel submission should not be significantly slower"
562 );
563 }
564
565 #[tokio::test]
567 async fn test_resource_management() {
568 let pool = WorkerPool::new(3);
569
570 assert_eq!(
572 pool.determine_worker_type("convert"),
573 WorkerType::CpuIntensive
574 );
575 assert_eq!(pool.determine_worker_type("sync"), WorkerType::Mixed);
576 assert_eq!(pool.determine_worker_type("match"), WorkerType::IoIntensive);
577 assert_eq!(
578 pool.determine_worker_type("validate"),
579 WorkerType::IoIntensive
580 );
581 assert_eq!(pool.determine_worker_type("unknown"), WorkerType::Mixed);
582
583 let stats = pool.get_worker_stats();
585 assert_eq!(stats.total_active, 0);
586 assert_eq!(stats.max_capacity, 3);
587 assert_eq!(stats.cpu_intensive_count, 0);
588 assert_eq!(stats.io_intensive_count, 0);
589 assert_eq!(stats.mixed_count, 0);
590
591 assert_eq!(pool.get_capacity(), 3);
593 assert_eq!(pool.get_active_count(), 0);
594 }
595
596 #[tokio::test]
598 async fn test_worker_pool_shutdown() {
599 use crate::core::parallel::task::{Task, TaskResult};
600 use async_trait::async_trait;
601
602 #[derive(Clone)]
603 struct SlowTask {
604 id: String,
605 }
606
607 #[async_trait]
608 impl Task for SlowTask {
609 async fn execute(&self) -> TaskResult {
610 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
611 TaskResult::Success(format!("slow-{}", self.id))
612 }
613 fn task_type(&self) -> &'static str {
614 "mixed"
615 }
616 fn task_id(&self) -> String {
617 self.id.clone()
618 }
619 }
620
621 let pool = WorkerPool::new(2);
622
623 for i in 0..2 {
625 let task = SlowTask {
626 id: format!("slow-{}", i),
627 };
628 pool.execute(Box::new(task)).await.unwrap();
629 }
630
631 assert!(pool.get_active_count() <= 2);
633
634 let start = std::time::Instant::now();
636 pool.shutdown().await;
637 let shutdown_time = start.elapsed();
638
639 assert!(shutdown_time >= std::time::Duration::from_millis(30));
641
642 assert_eq!(pool.get_active_count(), 0);
644 }
645
646 #[tokio::test]
648 async fn test_active_worker_tracking() {
649 use crate::core::parallel::task::{Task, TaskResult};
650 use async_trait::async_trait;
651
652 #[derive(Clone)]
653 struct TrackableTask {
654 id: String,
655 task_type: &'static str,
656 }
657
658 #[async_trait]
659 impl Task for TrackableTask {
660 async fn execute(&self) -> TaskResult {
661 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
662 TaskResult::Success(format!("tracked-{}", self.id))
663 }
664 fn task_type(&self) -> &'static str {
665 self.task_type
666 }
667 fn task_id(&self) -> String {
668 self.id.clone()
669 }
670 }
671
672 let pool = WorkerPool::new(3);
673
674 let tasks = vec![
676 ("cpu-task", "convert"),
677 ("io-task", "match"),
678 ("mixed-task", "sync"),
679 ];
680
681 for (id, task_type) in tasks {
682 let task = TrackableTask {
683 id: id.to_string(),
684 task_type,
685 };
686 pool.execute(Box::new(task)).await.unwrap();
687 }
688
689 let active_workers = pool.list_active_workers();
691 assert!(active_workers.len() <= 3, "Should not exceed pool capacity");
692
693 for worker in &active_workers {
695 assert!(!worker.task_id.is_empty(), "Task ID should be set");
696 assert!(matches!(
697 worker.worker_type,
698 WorkerType::CpuIntensive | WorkerType::IoIntensive | WorkerType::Mixed
699 ));
700 assert!(
701 worker.runtime.as_millis() < u128::MAX,
702 "Runtime should be valid"
703 );
704 }
705
706 let stats = pool.get_worker_stats();
708 assert!(stats.total_active <= 3);
709 assert_eq!(stats.max_capacity, 3);
710
711 tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
713 }
714
715 #[test]
716 fn worker_id_is_uuidv7() {
717 let w = Worker::new();
718 assert_eq!(w.id().get_version_num(), 7);
719 }
720
721 #[test]
722 fn consecutive_workers_have_distinct_ids() {
723 let a = Worker::new();
724 let b = Worker::new();
725 assert_ne!(a.id(), b.id());
726 }
727
728 #[tokio::test]
729 async fn worker_pool_execute_dispatches_uuidv7_worker_id() {
730 use crate::core::parallel::task::{Task, TaskResult};
731 use async_trait::async_trait;
732
733 struct DummyTask;
734
735 #[async_trait]
736 impl Task for DummyTask {
737 async fn execute(&self) -> TaskResult {
738 TaskResult::Success("done".into())
739 }
740 fn task_type(&self) -> &'static str {
741 "match"
742 }
743 fn task_id(&self) -> String {
744 "dummy".into()
745 }
746 }
747
748 let pool = WorkerPool::new(1);
749 let res = pool.execute(Box::new(DummyTask)).await;
750 assert!(matches!(res, Ok(TaskResult::Success(_))));
751
752 let workers = pool.list_active_workers();
753 assert_eq!(workers.len(), 1);
754 assert_eq!(workers[0].worker_id.get_version_num(), 7);
755 }
756}