Skip to main content

oxigeo_cli/util/
parallel.rs

1//! Parallel processing framework for CLI operations
2//!
3//! Provides comprehensive parallel processing capabilities including:
4//! - Thread pool management and configuration
5//! - Parallel file processing with progress tracking
6//! - Work distribution across threads with load balancing
7//! - Progress aggregation across multiple operations
8//! - Error collection and handling
9//! - Resource management with memory limits
10//! - Batch operations with configurable sizes
11//! - Pipeline execution for chained operations
12
13// Allow dead code for this module as it provides utility functions
14// that may be used in the future
15#![allow(dead_code)]
16
17use anyhow::{Context, Result};
18use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
19use rayon::prelude::*;
20use std::collections::HashMap;
21use std::path::{Path, PathBuf};
22use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
23use std::sync::{Arc, Mutex, RwLock};
24use std::time::{Duration, Instant};
25
26// ============================================================================
27// Thread Pool Configuration
28// ============================================================================
29
30/// Thread pool configuration for parallel operations
31#[derive(Debug, Clone)]
32pub struct ThreadPoolConfig {
33    /// Number of threads (None = auto-detect)
34    pub num_threads: Option<usize>,
35    /// Stack size per thread in bytes
36    pub stack_size: Option<usize>,
37    /// Thread name prefix
38    pub thread_name_prefix: String,
39    /// Enable thread pinning (CPU affinity)
40    pub pin_threads: bool,
41    /// Priority level for threads
42    pub priority: ThreadPriority,
43}
44
45/// Thread priority levels
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub enum ThreadPriority {
48    /// Low priority (background tasks)
49    Low,
50    /// Normal priority (default)
51    #[default]
52    Normal,
53    /// High priority (time-sensitive tasks)
54    High,
55}
56
57impl Default for ThreadPoolConfig {
58    fn default() -> Self {
59        Self {
60            num_threads: None,
61            stack_size: Some(8 * 1024 * 1024), // 8 MB
62            thread_name_prefix: "oxigeo-worker".to_string(),
63            pin_threads: false,
64            priority: ThreadPriority::Normal,
65        }
66    }
67}
68
69impl ThreadPoolConfig {
70    /// Create a new thread pool configuration
71    pub fn new() -> Self {
72        Self::default()
73    }
74
75    /// Set number of threads
76    pub fn with_num_threads(mut self, num: usize) -> Self {
77        self.num_threads = Some(num);
78        self
79    }
80
81    /// Set stack size
82    pub fn with_stack_size(mut self, size: usize) -> Self {
83        self.stack_size = Some(size);
84        self
85    }
86
87    /// Set thread name prefix
88    pub fn with_name_prefix(mut self, prefix: impl Into<String>) -> Self {
89        self.thread_name_prefix = prefix.into();
90        self
91    }
92
93    /// Enable thread pinning
94    pub fn with_pin_threads(mut self, pin: bool) -> Self {
95        self.pin_threads = pin;
96        self
97    }
98
99    /// Set thread priority
100    pub fn with_priority(mut self, priority: ThreadPriority) -> Self {
101        self.priority = priority;
102        self
103    }
104}
105
106/// Initialize global thread pool with custom configuration
107pub fn init_thread_pool(config: ThreadPoolConfig) -> Result<()> {
108    let mut builder = rayon::ThreadPoolBuilder::new();
109
110    if let Some(num_threads) = config.num_threads {
111        builder = builder.num_threads(num_threads);
112    }
113
114    if let Some(stack_size) = config.stack_size {
115        builder = builder.stack_size(stack_size);
116    }
117
118    let prefix = config.thread_name_prefix;
119    builder = builder.thread_name(move |idx| format!("{}-{}", prefix, idx));
120
121    builder
122        .build_global()
123        .context("Failed to initialize thread pool")?;
124
125    Ok(())
126}
127
128/// Get optimal number of threads for current system
129pub fn optimal_thread_count() -> usize {
130    num_cpus::get()
131}
132
133/// Get optimal number of physical cores
134pub fn physical_core_count() -> usize {
135    num_cpus::get_physical()
136}
137
138// ============================================================================
139// Work Distribution
140// ============================================================================
141
142/// Work item with priority and metadata
143#[derive(Debug, Clone)]
144pub struct WorkItem<T> {
145    /// The actual work data
146    pub data: T,
147    /// Priority (higher = processed first)
148    pub priority: i32,
149    /// Estimated cost/time for load balancing
150    pub estimated_cost: u64,
151    /// Optional group identifier for grouping related work
152    pub group_id: Option<String>,
153}
154
155impl<T> WorkItem<T> {
156    /// Create a new work item with default priority
157    pub fn new(data: T) -> Self {
158        Self {
159            data,
160            priority: 0,
161            estimated_cost: 1,
162            group_id: None,
163        }
164    }
165
166    /// Set priority
167    pub fn with_priority(mut self, priority: i32) -> Self {
168        self.priority = priority;
169        self
170    }
171
172    /// Set estimated cost
173    pub fn with_cost(mut self, cost: u64) -> Self {
174        self.estimated_cost = cost;
175        self
176    }
177
178    /// Set group ID
179    pub fn with_group(mut self, group: impl Into<String>) -> Self {
180        self.group_id = Some(group.into());
181        self
182    }
183}
184
185/// Work distribution strategy
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187pub enum DistributionStrategy {
188    /// Even distribution across threads
189    #[default]
190    RoundRobin,
191    /// Dynamic work stealing (default rayon behavior)
192    WorkStealing,
193    /// Cost-based load balancing
194    LoadBalanced,
195    /// Process by priority
196    PriorityBased,
197}
198
199/// Work distributor for parallel operations
200pub struct WorkDistributor<T> {
201    items: Vec<WorkItem<T>>,
202    strategy: DistributionStrategy,
203    chunk_size: Option<usize>,
204}
205
206impl<T: Send + Sync> WorkDistributor<T> {
207    /// Create a new work distributor
208    pub fn new(items: Vec<WorkItem<T>>) -> Self {
209        Self {
210            items,
211            strategy: DistributionStrategy::default(),
212            chunk_size: None,
213        }
214    }
215
216    /// Create from raw items (wraps in WorkItem)
217    pub fn from_items(items: impl IntoIterator<Item = T>) -> Self {
218        let work_items: Vec<WorkItem<T>> = items.into_iter().map(WorkItem::new).collect();
219        Self::new(work_items)
220    }
221
222    /// Set distribution strategy
223    pub fn with_strategy(mut self, strategy: DistributionStrategy) -> Self {
224        self.strategy = strategy;
225        self
226    }
227
228    /// Set chunk size for processing
229    pub fn with_chunk_size(mut self, size: usize) -> Self {
230        self.chunk_size = Some(size);
231        self
232    }
233
234    /// Distribute and process work items
235    pub fn process<R, F>(&mut self, processor: F) -> Result<Vec<R>>
236    where
237        R: Send,
238        F: Fn(&T) -> Result<R> + Send + Sync,
239    {
240        // Sort items based on strategy
241        match self.strategy {
242            DistributionStrategy::PriorityBased => {
243                self.items
244                    .sort_by_key(|item| std::cmp::Reverse(item.priority));
245            }
246            DistributionStrategy::LoadBalanced => {
247                // Sort by estimated cost (largest first for better load balancing)
248                self.items
249                    .sort_by_key(|item| std::cmp::Reverse(item.estimated_cost));
250            }
251            _ => {}
252        }
253
254        // Process items in parallel
255        let results: Result<Vec<R>> = self
256            .items
257            .par_iter()
258            .map(|item| processor(&item.data))
259            .collect();
260
261        results
262    }
263
264    /// Process with progress tracking
265    pub fn process_with_progress<R, F>(
266        &mut self,
267        processor: F,
268        progress_message: &str,
269    ) -> Result<Vec<R>>
270    where
271        R: Send,
272        F: Fn(&T) -> Result<R> + Send + Sync,
273    {
274        let pb = ProgressBar::new(self.items.len() as u64);
275        pb.set_style(
276            ProgressStyle::default_bar()
277                .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
278                .unwrap_or_else(|_| ProgressStyle::default_bar())
279                .progress_chars("=>-"),
280        );
281        pb.set_message(progress_message.to_string());
282
283        let results: Result<Vec<R>> = self
284            .items
285            .par_iter()
286            .map(|item| {
287                let result = processor(&item.data);
288                pb.inc(1);
289                result
290            })
291            .collect();
292
293        pb.finish_with_message(format!("{}: complete", progress_message));
294
295        results
296    }
297}
298
299// ============================================================================
300// Progress Aggregation
301// ============================================================================
302
303/// Progress statistics for a single operation
304#[derive(Debug, Clone)]
305pub struct ProgressStats {
306    /// Total items to process
307    pub total: u64,
308    /// Items completed successfully
309    pub completed: u64,
310    /// Items that failed
311    pub failed: u64,
312    /// Items currently in progress
313    pub in_progress: u64,
314    /// Start time
315    pub start_time: Instant,
316    /// Bytes processed (if applicable)
317    pub bytes_processed: u64,
318}
319
320impl Default for ProgressStats {
321    fn default() -> Self {
322        Self {
323            total: 0,
324            completed: 0,
325            failed: 0,
326            in_progress: 0,
327            start_time: Instant::now(),
328            bytes_processed: 0,
329        }
330    }
331}
332
333impl ProgressStats {
334    /// Calculate throughput (items per second)
335    pub fn items_per_second(&self) -> f64 {
336        let elapsed = self.start_time.elapsed().as_secs_f64();
337        if elapsed > 0.0 {
338            self.completed as f64 / elapsed
339        } else {
340            0.0
341        }
342    }
343
344    /// Calculate bytes throughput
345    pub fn bytes_per_second(&self) -> f64 {
346        let elapsed = self.start_time.elapsed().as_secs_f64();
347        if elapsed > 0.0 {
348            self.bytes_processed as f64 / elapsed
349        } else {
350            0.0
351        }
352    }
353
354    /// Estimate time remaining
355    pub fn estimated_remaining(&self) -> Duration {
356        let remaining = self.total.saturating_sub(self.completed + self.failed);
357        let rate = self.items_per_second();
358        if rate > 0.0 {
359            Duration::from_secs_f64(remaining as f64 / rate)
360        } else {
361            Duration::MAX
362        }
363    }
364
365    /// Get completion percentage
366    pub fn percent_complete(&self) -> f64 {
367        if self.total > 0 {
368            ((self.completed + self.failed) as f64 / self.total as f64) * 100.0
369        } else {
370            0.0
371        }
372    }
373}
374
375/// Aggregated progress across multiple operations
376pub struct ProgressAggregator {
377    operations: Arc<RwLock<HashMap<String, ProgressStats>>>,
378    multi_progress: Arc<MultiProgress>,
379    progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>>,
380}
381
382impl ProgressAggregator {
383    /// Create a new progress aggregator
384    pub fn new() -> Self {
385        Self {
386            operations: Arc::new(RwLock::new(HashMap::new())),
387            multi_progress: Arc::new(MultiProgress::new()),
388            progress_bars: Arc::new(Mutex::new(HashMap::new())),
389        }
390    }
391
392    /// Register a new operation
393    pub fn register_operation(&self, name: &str, total: u64) -> Result<()> {
394        let stats = ProgressStats {
395            total,
396            ..Default::default()
397        };
398
399        let mut ops = self
400            .operations
401            .write()
402            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
403        ops.insert(name.to_string(), stats);
404
405        // Create progress bar
406        let pb = self.multi_progress.add(ProgressBar::new(total));
407        pb.set_style(
408            ProgressStyle::default_bar()
409                .template("{spinner:.green} {msg}: [{bar:40.cyan/blue}] {pos}/{len}")
410                .unwrap_or_else(|_| ProgressStyle::default_bar())
411                .progress_chars("=>-"),
412        );
413        pb.set_message(name.to_string());
414
415        let mut pbs = self
416            .progress_bars
417            .lock()
418            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
419        pbs.insert(name.to_string(), pb);
420
421        Ok(())
422    }
423
424    /// Update operation progress
425    pub fn update(&self, name: &str, completed_delta: u64, failed_delta: u64) -> Result<()> {
426        let mut ops = self
427            .operations
428            .write()
429            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
430
431        if let Some(stats) = ops.get_mut(name) {
432            stats.completed += completed_delta;
433            stats.failed += failed_delta;
434        }
435
436        let pbs = self
437            .progress_bars
438            .lock()
439            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
440        if let Some(pb) = pbs.get(name) {
441            pb.inc(completed_delta + failed_delta);
442        }
443
444        Ok(())
445    }
446
447    /// Update bytes processed
448    pub fn update_bytes(&self, name: &str, bytes: u64) -> Result<()> {
449        let mut ops = self
450            .operations
451            .write()
452            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
453
454        if let Some(stats) = ops.get_mut(name) {
455            stats.bytes_processed += bytes;
456        }
457
458        Ok(())
459    }
460
461    /// Get aggregated statistics
462    pub fn get_aggregate_stats(&self) -> Result<ProgressStats> {
463        let ops = self
464            .operations
465            .read()
466            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
467
468        let mut aggregate = ProgressStats::default();
469        for stats in ops.values() {
470            aggregate.total += stats.total;
471            aggregate.completed += stats.completed;
472            aggregate.failed += stats.failed;
473            aggregate.in_progress += stats.in_progress;
474            aggregate.bytes_processed += stats.bytes_processed;
475        }
476
477        Ok(aggregate)
478    }
479
480    /// Finish an operation
481    pub fn finish_operation(&self, name: &str, message: &str) -> Result<()> {
482        let pbs = self
483            .progress_bars
484            .lock()
485            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
486        if let Some(pb) = pbs.get(name) {
487            pb.finish_with_message(message.to_string());
488        }
489        Ok(())
490    }
491}
492
493impl Default for ProgressAggregator {
494    fn default() -> Self {
495        Self::new()
496    }
497}
498
499// ============================================================================
500// Error Collection
501// ============================================================================
502
503/// Error information with context
504#[derive(Debug, Clone)]
505pub struct ErrorInfo {
506    /// Error message
507    pub message: String,
508    /// Source file/item that caused the error
509    pub source: Option<String>,
510    /// When the error occurred
511    pub timestamp: Instant,
512    /// Is this error recoverable?
513    pub recoverable: bool,
514    /// Additional context
515    pub context: HashMap<String, String>,
516}
517
518impl ErrorInfo {
519    /// Create a new error info
520    pub fn new(message: impl Into<String>) -> Self {
521        Self {
522            message: message.into(),
523            source: None,
524            timestamp: Instant::now(),
525            recoverable: true,
526            context: HashMap::new(),
527        }
528    }
529
530    /// Set source
531    pub fn with_source(mut self, source: impl Into<String>) -> Self {
532        self.source = Some(source.into());
533        self
534    }
535
536    /// Set recoverable flag
537    pub fn with_recoverable(mut self, recoverable: bool) -> Self {
538        self.recoverable = recoverable;
539        self
540    }
541
542    /// Add context
543    pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
544        self.context.insert(key.into(), value.into());
545        self
546    }
547}
548
549/// Error collector for parallel operations
550pub struct ErrorCollector {
551    errors: Arc<Mutex<Vec<ErrorInfo>>>,
552    max_errors: usize,
553    stop_on_error: Arc<AtomicBool>,
554    error_count: Arc<AtomicUsize>,
555}
556
557impl ErrorCollector {
558    /// Create a new error collector
559    pub fn new() -> Self {
560        Self {
561            errors: Arc::new(Mutex::new(Vec::new())),
562            max_errors: 1000,
563            stop_on_error: Arc::new(AtomicBool::new(false)),
564            error_count: Arc::new(AtomicUsize::new(0)),
565        }
566    }
567
568    /// Set maximum errors to collect
569    pub fn with_max_errors(mut self, max: usize) -> Self {
570        self.max_errors = max;
571        self
572    }
573
574    /// Enable stop on first error
575    pub fn with_stop_on_error(self, stop: bool) -> Self {
576        self.stop_on_error.store(stop, Ordering::SeqCst);
577        self
578    }
579
580    /// Check if should stop processing
581    pub fn should_stop(&self) -> bool {
582        self.stop_on_error.load(Ordering::SeqCst) && self.error_count.load(Ordering::SeqCst) > 0
583    }
584
585    /// Add an error
586    pub fn add_error(&self, error: ErrorInfo) -> Result<()> {
587        let count = self.error_count.fetch_add(1, Ordering::SeqCst);
588        if count < self.max_errors {
589            let mut errors = self
590                .errors
591                .lock()
592                .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
593            errors.push(error);
594        }
595        Ok(())
596    }
597
598    /// Add error from Result
599    pub fn collect<T>(&self, result: Result<T>, source: Option<&str>) -> Option<T> {
600        match result {
601            Ok(value) => Some(value),
602            Err(e) => {
603                let mut error_info = ErrorInfo::new(format!("{:?}", e));
604                if let Some(src) = source {
605                    error_info = error_info.with_source(src);
606                }
607                // Ignore errors when adding to collector (best effort)
608                let _ = self.add_error(error_info);
609                None
610            }
611        }
612    }
613
614    /// Get all collected errors
615    pub fn get_errors(&self) -> Result<Vec<ErrorInfo>> {
616        let errors = self
617            .errors
618            .lock()
619            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
620        Ok(errors.clone())
621    }
622
623    /// Get error count
624    pub fn error_count(&self) -> usize {
625        self.error_count.load(Ordering::SeqCst)
626    }
627
628    /// Check if there are any errors
629    pub fn has_errors(&self) -> bool {
630        self.error_count.load(Ordering::SeqCst) > 0
631    }
632
633    /// Get summary of errors
634    pub fn summary(&self) -> Result<String> {
635        let errors = self.get_errors()?;
636        let total = self.error_count();
637
638        if errors.is_empty() {
639            return Ok("No errors".to_string());
640        }
641
642        let mut summary = format!("Total errors: {}\n", total);
643        for (i, error) in errors.iter().enumerate().take(10) {
644            summary.push_str(&format!(
645                "  {}. {} (source: {})\n",
646                i + 1,
647                error.message,
648                error.source.as_deref().unwrap_or("unknown")
649            ));
650        }
651
652        if total > 10 {
653            summary.push_str(&format!("  ... and {} more errors\n", total - 10));
654        }
655
656        Ok(summary)
657    }
658}
659
660impl Default for ErrorCollector {
661    fn default() -> Self {
662        Self::new()
663    }
664}
665
666// ============================================================================
667// Parallel File Processing
668// ============================================================================
669
670/// Parallel file processor with advanced features
671pub struct ParallelFileProcessor {
672    multi_progress: Arc<MultiProgress>,
673    file_count: Arc<AtomicUsize>,
674    error_count: Arc<AtomicUsize>,
675    bytes_processed: Arc<AtomicU64>,
676    error_collector: ErrorCollector,
677}
678
679impl ParallelFileProcessor {
680    /// Create a new parallel file processor
681    pub fn new() -> Self {
682        Self {
683            multi_progress: Arc::new(MultiProgress::new()),
684            file_count: Arc::new(AtomicUsize::new(0)),
685            error_count: Arc::new(AtomicUsize::new(0)),
686            bytes_processed: Arc::new(AtomicU64::new(0)),
687            error_collector: ErrorCollector::new(),
688        }
689    }
690
691    /// Process files in parallel with a custom function
692    pub fn process_files<P, F>(
693        &self,
694        files: Vec<P>,
695        processor: F,
696        progress_message: &str,
697    ) -> Result<Vec<Result<()>>>
698    where
699        P: AsRef<Path> + Send + Sync,
700        F: Fn(&Path) -> Result<()> + Send + Sync,
701    {
702        let pb = self
703            .multi_progress
704            .add(ProgressBar::new(files.len() as u64));
705        pb.set_style(
706            ProgressStyle::default_bar()
707                .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
708                .unwrap_or_else(|_| ProgressStyle::default_bar())
709                .progress_chars("=>-"),
710        );
711        pb.set_message(progress_message.to_string());
712
713        let results: Vec<Result<()>> = files
714            .par_iter()
715            .map(|file| {
716                let file_path = file.as_ref();
717                let result = processor(file_path);
718
719                if result.is_ok() {
720                    self.file_count.fetch_add(1, Ordering::SeqCst);
721                } else {
722                    self.error_count.fetch_add(1, Ordering::SeqCst);
723                }
724
725                pb.inc(1);
726                result
727            })
728            .collect();
729
730        pb.finish_with_message(format!(
731            "{}: {} succeeded, {} failed",
732            progress_message,
733            self.file_count.load(Ordering::SeqCst),
734            self.error_count.load(Ordering::SeqCst)
735        ));
736
737        Ok(results)
738    }
739
740    /// Process files with result collection
741    pub fn process_files_with_results<P, T, F>(
742        &self,
743        files: Vec<P>,
744        processor: F,
745        progress_message: &str,
746    ) -> Result<Vec<(PathBuf, Result<T>)>>
747    where
748        P: AsRef<Path> + Send + Sync,
749        T: Send,
750        F: Fn(&Path) -> Result<T> + Send + Sync,
751    {
752        let pb = self
753            .multi_progress
754            .add(ProgressBar::new(files.len() as u64));
755        pb.set_style(
756            ProgressStyle::default_bar()
757                .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA: {eta})")
758                .unwrap_or_else(|_| ProgressStyle::default_bar())
759                .progress_chars("=>-"),
760        );
761        pb.set_message(progress_message.to_string());
762
763        let results: Vec<(PathBuf, Result<T>)> = files
764            .par_iter()
765            .map(|file| {
766                let file_path = file.as_ref();
767                let result = processor(file_path);
768
769                if result.is_ok() {
770                    self.file_count.fetch_add(1, Ordering::SeqCst);
771                } else {
772                    self.error_count.fetch_add(1, Ordering::SeqCst);
773                }
774
775                pb.inc(1);
776                (file_path.to_path_buf(), result)
777            })
778            .collect();
779
780        pb.finish_with_message(format!(
781            "{}: {} succeeded, {} failed",
782            progress_message,
783            self.file_count.load(Ordering::SeqCst),
784            self.error_count.load(Ordering::SeqCst)
785        ));
786
787        Ok(results)
788    }
789
790    /// Add bytes processed
791    pub fn add_bytes(&self, bytes: u64) {
792        self.bytes_processed.fetch_add(bytes, Ordering::SeqCst);
793    }
794
795    /// Get statistics
796    pub fn stats(&self) -> (usize, usize, u64) {
797        (
798            self.file_count.load(Ordering::SeqCst),
799            self.error_count.load(Ordering::SeqCst),
800            self.bytes_processed.load(Ordering::SeqCst),
801        )
802    }
803
804    /// Get error collector
805    pub fn error_collector(&self) -> &ErrorCollector {
806        &self.error_collector
807    }
808}
809
810impl Default for ParallelFileProcessor {
811    fn default() -> Self {
812        Self::new()
813    }
814}
815
816// ============================================================================
817// Parallel Band Processor
818// ============================================================================
819
820/// Parallel band processor for multi-band raster operations
821pub struct ParallelBandProcessor {
822    multi_progress: Arc<MultiProgress>,
823}
824
825impl ParallelBandProcessor {
826    /// Create a new parallel band processor
827    pub fn new() -> Self {
828        Self {
829            multi_progress: Arc::new(MultiProgress::new()),
830        }
831    }
832
833    /// Process bands in parallel
834    pub fn process_bands<T, F>(
835        &self,
836        band_indices: Vec<usize>,
837        processor: F,
838        progress_message: &str,
839    ) -> Result<Vec<T>>
840    where
841        T: Send,
842        F: Fn(usize) -> Result<T> + Send + Sync,
843    {
844        let pb = self
845            .multi_progress
846            .add(ProgressBar::new(band_indices.len() as u64));
847        pb.set_style(
848            ProgressStyle::default_bar()
849                .template("{msg} [{bar:40.green/blue}] {pos}/{len}")
850                .unwrap_or_else(|_| ProgressStyle::default_bar())
851                .progress_chars("=>-"),
852        );
853        pb.set_message(progress_message.to_string());
854
855        let results: Result<Vec<T>> = band_indices
856            .par_iter()
857            .map(|&idx| {
858                let result = processor(idx);
859                pb.inc(1);
860                result
861            })
862            .collect();
863
864        pb.finish_with_message(format!("{}: complete", progress_message));
865
866        results
867    }
868}
869
870impl Default for ParallelBandProcessor {
871    fn default() -> Self {
872        Self::new()
873    }
874}
875
876// ============================================================================
877// Parallel Tile Processor
878// ============================================================================
879
880/// Parallel tile processor for chunked raster operations
881pub struct ParallelTileProcessor {
882    tile_size: (usize, usize),
883    multi_progress: Arc<MultiProgress>,
884}
885
886impl ParallelTileProcessor {
887    /// Create a new parallel tile processor
888    pub fn new(tile_width: usize, tile_height: usize) -> Self {
889        Self {
890            tile_size: (tile_width, tile_height),
891            multi_progress: Arc::new(MultiProgress::new()),
892        }
893    }
894
895    /// Get tile size
896    pub fn tile_size(&self) -> (usize, usize) {
897        self.tile_size
898    }
899
900    /// Process raster in tiles with overlap
901    pub fn process_tiles<T, F>(
902        &self,
903        raster_width: usize,
904        raster_height: usize,
905        overlap: usize,
906        processor: F,
907        progress_message: &str,
908    ) -> Result<Vec<T>>
909    where
910        T: Send,
911        F: Fn(usize, usize, usize, usize) -> Result<T> + Send + Sync,
912    {
913        let (tile_w, tile_h) = self.tile_size;
914
915        // Calculate tile grid
916        let tiles_x = raster_width.div_ceil(tile_w);
917        let tiles_y = raster_height.div_ceil(tile_h);
918        let total_tiles = tiles_x * tiles_y;
919
920        let pb = self
921            .multi_progress
922            .add(ProgressBar::new(total_tiles as u64));
923        pb.set_style(
924            ProgressStyle::default_bar()
925                .template("{msg} [{bar:40.yellow/blue}] {pos}/{len} tiles")
926                .unwrap_or_else(|_| ProgressStyle::default_bar())
927                .progress_chars("=>-"),
928        );
929        pb.set_message(progress_message.to_string());
930
931        let mut tile_specs = Vec::with_capacity(total_tiles);
932        for ty in 0..tiles_y {
933            for tx in 0..tiles_x {
934                let x_start = tx * tile_w;
935                let y_start = ty * tile_h;
936                let x_end = (x_start + tile_w + overlap).min(raster_width);
937                let y_end = (y_start + tile_h + overlap).min(raster_height);
938
939                tile_specs.push((x_start, y_start, x_end - x_start, y_end - y_start));
940            }
941        }
942
943        let results: Result<Vec<T>> = tile_specs
944            .par_iter()
945            .map(|&(x, y, w, h)| {
946                let result = processor(x, y, w, h);
947                pb.inc(1);
948                result
949            })
950            .collect();
951
952        pb.finish_with_message(format!("{}: complete", progress_message));
953
954        results
955    }
956}
957
958// ============================================================================
959// Batch Operations
960// ============================================================================
961
962/// Batch operation manager
963pub struct BatchManager {
964    batch_size: usize,
965    multi_progress: Arc<MultiProgress>,
966}
967
968impl BatchManager {
969    /// Create a new batch manager
970    pub fn new(batch_size: usize) -> Self {
971        Self {
972            batch_size: batch_size.max(1),
973            multi_progress: Arc::new(MultiProgress::new()),
974        }
975    }
976
977    /// Get batch size
978    pub fn batch_size(&self) -> usize {
979        self.batch_size
980    }
981
982    /// Process items in batches
983    pub fn process_batches<T, R, F>(
984        &self,
985        items: Vec<T>,
986        processor: F,
987        progress_message: &str,
988    ) -> Result<Vec<R>>
989    where
990        T: Send + Clone,
991        R: Send,
992        F: Fn(Vec<T>) -> Result<Vec<R>> + Send + Sync,
993    {
994        let batches: Vec<Vec<T>> = items
995            .chunks(self.batch_size)
996            .map(|chunk| chunk.to_vec())
997            .collect();
998
999        let pb = self
1000            .multi_progress
1001            .add(ProgressBar::new(batches.len() as u64));
1002        pb.set_style(
1003            ProgressStyle::default_bar()
1004                .template("{msg} [{bar:40.magenta/blue}] {pos}/{len} batches")
1005                .unwrap_or_else(|_| ProgressStyle::default_bar())
1006                .progress_chars("=>-"),
1007        );
1008        pb.set_message(progress_message.to_string());
1009
1010        let results: Result<Vec<Vec<R>>> = batches
1011            .into_par_iter()
1012            .map(|batch| {
1013                let result = processor(batch);
1014                pb.inc(1);
1015                result
1016            })
1017            .collect();
1018
1019        pb.finish_with_message(format!("{}: complete", progress_message));
1020
1021        results.map(|batches| batches.into_iter().flatten().collect())
1022    }
1023}
1024
1025// ============================================================================
1026// Resource Management
1027// ============================================================================
1028
1029/// Resource manager for parallel operations
1030pub struct ResourceManager {
1031    max_memory_bytes: Arc<Mutex<usize>>,
1032    current_memory_bytes: Arc<Mutex<usize>>,
1033    max_threads: usize,
1034    active_threads: Arc<AtomicUsize>,
1035}
1036
1037impl ResourceManager {
1038    /// Create a new resource manager
1039    pub fn new(max_memory_mb: usize, max_threads: usize) -> Self {
1040        Self {
1041            max_memory_bytes: Arc::new(Mutex::new(max_memory_mb * 1024 * 1024)),
1042            current_memory_bytes: Arc::new(Mutex::new(0)),
1043            max_threads: max_threads.max(1),
1044            active_threads: Arc::new(AtomicUsize::new(0)),
1045        }
1046    }
1047
1048    /// Try to allocate memory
1049    pub fn try_allocate(&self, bytes: usize) -> Result<bool> {
1050        let mut current = self
1051            .current_memory_bytes
1052            .lock()
1053            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1054        let max = self
1055            .max_memory_bytes
1056            .lock()
1057            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1058
1059        if *current + bytes <= *max {
1060            *current += bytes;
1061            Ok(true)
1062        } else {
1063            Ok(false)
1064        }
1065    }
1066
1067    /// Release allocated memory
1068    pub fn release(&self, bytes: usize) -> Result<()> {
1069        let mut current = self
1070            .current_memory_bytes
1071            .lock()
1072            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1073
1074        *current = current.saturating_sub(bytes);
1075
1076        Ok(())
1077    }
1078
1079    /// Get current memory usage in MB
1080    pub fn current_usage_mb(&self) -> Result<f64> {
1081        let current = self
1082            .current_memory_bytes
1083            .lock()
1084            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1085        Ok(*current as f64 / (1024.0 * 1024.0))
1086    }
1087
1088    /// Get max memory in MB
1089    pub fn max_memory_mb(&self) -> Result<f64> {
1090        let max = self
1091            .max_memory_bytes
1092            .lock()
1093            .map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
1094        Ok(*max as f64 / (1024.0 * 1024.0))
1095    }
1096
1097    /// Get max threads
1098    pub fn max_threads(&self) -> usize {
1099        self.max_threads
1100    }
1101
1102    /// Acquire a thread slot
1103    pub fn acquire_thread(&self) -> bool {
1104        let current = self.active_threads.fetch_add(1, Ordering::SeqCst);
1105        if current >= self.max_threads {
1106            self.active_threads.fetch_sub(1, Ordering::SeqCst);
1107            false
1108        } else {
1109            true
1110        }
1111    }
1112
1113    /// Release a thread slot
1114    pub fn release_thread(&self) {
1115        self.active_threads.fetch_sub(1, Ordering::SeqCst);
1116    }
1117
1118    /// Get active thread count
1119    pub fn active_threads(&self) -> usize {
1120        self.active_threads.load(Ordering::SeqCst)
1121    }
1122}
1123
1124// ============================================================================
1125// Pipeline Execution
1126// ============================================================================
1127
1128/// Pipeline stage trait
1129pub trait PipelineStage<I, O>: Send + Sync {
1130    /// Process input and produce output
1131    fn process(&self, input: I) -> Result<O>;
1132
1133    /// Get stage name
1134    fn name(&self) -> &str;
1135}
1136
1137/// Simple function-based pipeline stage
1138pub struct FnStage<I, O, F>
1139where
1140    I: Send + Sync,
1141    O: Send + Sync,
1142    F: Fn(I) -> Result<O> + Send + Sync,
1143{
1144    name: String,
1145    func: F,
1146    _phantom: std::marker::PhantomData<(I, O)>,
1147}
1148
1149impl<I, O, F> FnStage<I, O, F>
1150where
1151    I: Send + Sync,
1152    O: Send + Sync,
1153    F: Fn(I) -> Result<O> + Send + Sync,
1154{
1155    /// Create a new function-based stage
1156    pub fn new(name: impl Into<String>, func: F) -> Self {
1157        Self {
1158            name: name.into(),
1159            func,
1160            _phantom: std::marker::PhantomData,
1161        }
1162    }
1163}
1164
1165impl<I, O, F> PipelineStage<I, O> for FnStage<I, O, F>
1166where
1167    I: Send + Sync,
1168    O: Send + Sync,
1169    F: Fn(I) -> Result<O> + Send + Sync,
1170{
1171    fn process(&self, input: I) -> Result<O> {
1172        (self.func)(input)
1173    }
1174
1175    fn name(&self) -> &str {
1176        &self.name
1177    }
1178}
1179
1180/// Pipeline execution result
1181#[derive(Debug)]
1182pub struct PipelineResult<T> {
1183    /// Successful results
1184    pub successes: Vec<T>,
1185    /// Failed items with error info
1186    pub failures: Vec<ErrorInfo>,
1187    /// Total processing time
1188    pub duration: Duration,
1189    /// Items processed per second
1190    pub throughput: f64,
1191}
1192
1193/// Pipeline executor for chained operations
1194pub struct PipelineExecutor {
1195    multi_progress: Arc<MultiProgress>,
1196    error_collector: ErrorCollector,
1197}
1198
1199impl PipelineExecutor {
1200    /// Create a new pipeline executor
1201    pub fn new() -> Self {
1202        Self {
1203            multi_progress: Arc::new(MultiProgress::new()),
1204            error_collector: ErrorCollector::new(),
1205        }
1206    }
1207
1208    /// Execute a two-stage pipeline
1209    pub fn execute_two_stage<I, M, O, S1, S2>(
1210        &self,
1211        inputs: Vec<I>,
1212        stage1: &S1,
1213        stage2: &S2,
1214        progress_message: &str,
1215    ) -> Result<PipelineResult<O>>
1216    where
1217        I: Send + Sync + Clone,
1218        M: Send + Sync,
1219        O: Send,
1220        S1: PipelineStage<I, M>,
1221        S2: PipelineStage<M, O>,
1222    {
1223        let start = Instant::now();
1224        let total = inputs.len() as u64;
1225
1226        let pb = self.multi_progress.add(ProgressBar::new(total));
1227        pb.set_style(
1228            ProgressStyle::default_bar()
1229                .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec})")
1230                .unwrap_or_else(|_| ProgressStyle::default_bar())
1231                .progress_chars("=>-"),
1232        );
1233        pb.set_message(progress_message.to_string());
1234
1235        let results: Vec<Result<O>> = inputs
1236            .par_iter()
1237            .map(|input| {
1238                let mid = stage1.process(input.clone())?;
1239                let output = stage2.process(mid)?;
1240                pb.inc(1);
1241                Ok(output)
1242            })
1243            .collect();
1244
1245        pb.finish_with_message(format!("{}: complete", progress_message));
1246
1247        let duration = start.elapsed();
1248        let mut successes = Vec::new();
1249        let mut failures = Vec::new();
1250
1251        for (i, result) in results.into_iter().enumerate() {
1252            match result {
1253                Ok(output) => successes.push(output),
1254                Err(e) => {
1255                    failures.push(
1256                        ErrorInfo::new(format!("{:?}", e)).with_source(format!("item_{}", i)),
1257                    );
1258                }
1259            }
1260        }
1261
1262        let throughput = if duration.as_secs_f64() > 0.0 {
1263            total as f64 / duration.as_secs_f64()
1264        } else {
1265            0.0
1266        };
1267
1268        Ok(PipelineResult {
1269            successes,
1270            failures,
1271            duration,
1272            throughput,
1273        })
1274    }
1275
1276    /// Execute a three-stage pipeline
1277    pub fn execute_three_stage<I, M1, M2, O, S1, S2, S3>(
1278        &self,
1279        inputs: Vec<I>,
1280        stage1: &S1,
1281        stage2: &S2,
1282        stage3: &S3,
1283        progress_message: &str,
1284    ) -> Result<PipelineResult<O>>
1285    where
1286        I: Send + Sync + Clone,
1287        M1: Send + Sync,
1288        M2: Send + Sync,
1289        O: Send,
1290        S1: PipelineStage<I, M1>,
1291        S2: PipelineStage<M1, M2>,
1292        S3: PipelineStage<M2, O>,
1293    {
1294        let start = Instant::now();
1295        let total = inputs.len() as u64;
1296
1297        let pb = self.multi_progress.add(ProgressBar::new(total));
1298        pb.set_style(
1299            ProgressStyle::default_bar()
1300                .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({per_sec})")
1301                .unwrap_or_else(|_| ProgressStyle::default_bar())
1302                .progress_chars("=>-"),
1303        );
1304        pb.set_message(progress_message.to_string());
1305
1306        let results: Vec<Result<O>> = inputs
1307            .par_iter()
1308            .map(|input| {
1309                let m1 = stage1.process(input.clone())?;
1310                let m2 = stage2.process(m1)?;
1311                let output = stage3.process(m2)?;
1312                pb.inc(1);
1313                Ok(output)
1314            })
1315            .collect();
1316
1317        pb.finish_with_message(format!("{}: complete", progress_message));
1318
1319        let duration = start.elapsed();
1320        let mut successes = Vec::new();
1321        let mut failures = Vec::new();
1322
1323        for (i, result) in results.into_iter().enumerate() {
1324            match result {
1325                Ok(output) => successes.push(output),
1326                Err(e) => {
1327                    failures.push(
1328                        ErrorInfo::new(format!("{:?}", e)).with_source(format!("item_{}", i)),
1329                    );
1330                }
1331            }
1332        }
1333
1334        let throughput = if duration.as_secs_f64() > 0.0 {
1335            total as f64 / duration.as_secs_f64()
1336        } else {
1337            0.0
1338        };
1339
1340        Ok(PipelineResult {
1341            successes,
1342            failures,
1343            duration,
1344            throughput,
1345        })
1346    }
1347
1348    /// Get error collector
1349    pub fn error_collector(&self) -> &ErrorCollector {
1350        &self.error_collector
1351    }
1352}
1353
1354impl Default for PipelineExecutor {
1355    fn default() -> Self {
1356        Self::new()
1357    }
1358}
1359
1360// ============================================================================
1361// Tests
1362// ============================================================================
1363
1364#[cfg(test)]
1365mod tests {
1366    use super::*;
1367
1368    #[test]
1369    fn test_optimal_thread_count() {
1370        let count = optimal_thread_count();
1371        assert!(count > 0);
1372    }
1373
1374    #[test]
1375    fn test_physical_core_count() {
1376        let count = physical_core_count();
1377        assert!(count > 0);
1378        // In containerized environments, physical cores can exceed cgroup-limited logical cores
1379        // so we only check that the count is positive and reasonable
1380        assert!(
1381            count <= 1024,
1382            "Physical core count seems unreasonable: {}",
1383            count
1384        );
1385    }
1386
1387    #[test]
1388    fn test_thread_pool_config() {
1389        let config = ThreadPoolConfig::new()
1390            .with_num_threads(4)
1391            .with_stack_size(4 * 1024 * 1024)
1392            .with_name_prefix("test-worker")
1393            .with_priority(ThreadPriority::High);
1394
1395        assert_eq!(config.num_threads, Some(4));
1396        assert_eq!(config.stack_size, Some(4 * 1024 * 1024));
1397        assert_eq!(config.thread_name_prefix, "test-worker");
1398        assert_eq!(config.priority, ThreadPriority::High);
1399    }
1400
1401    #[test]
1402    fn test_work_item() {
1403        let item = WorkItem::new(42)
1404            .with_priority(10)
1405            .with_cost(100)
1406            .with_group("test-group");
1407
1408        assert_eq!(item.data, 42);
1409        assert_eq!(item.priority, 10);
1410        assert_eq!(item.estimated_cost, 100);
1411        assert_eq!(item.group_id, Some("test-group".to_string()));
1412    }
1413
1414    #[test]
1415    fn test_work_distributor() {
1416        let items: Vec<i32> = (0..100).collect();
1417        let mut distributor =
1418            WorkDistributor::from_items(items).with_strategy(DistributionStrategy::WorkStealing);
1419
1420        let results = distributor
1421            .process(|&x| Ok(x * 2))
1422            .expect("Processing failed");
1423
1424        assert_eq!(results.len(), 100);
1425    }
1426
1427    #[test]
1428    fn test_progress_stats() {
1429        let stats = ProgressStats {
1430            total: 100,
1431            completed: 50,
1432            failed: 5,
1433            ..Default::default()
1434        };
1435
1436        assert!((stats.percent_complete() - 55.0).abs() < 0.001);
1437    }
1438
1439    #[test]
1440    fn test_error_collector() {
1441        let collector = ErrorCollector::new();
1442
1443        let error = ErrorInfo::new("Test error")
1444            .with_source("test.txt")
1445            .with_recoverable(true);
1446
1447        collector.add_error(error).expect("Failed to add error");
1448
1449        assert!(collector.has_errors());
1450        assert_eq!(collector.error_count(), 1);
1451
1452        let errors = collector.get_errors().expect("Failed to get errors");
1453        assert_eq!(errors.len(), 1);
1454        assert_eq!(errors[0].message, "Test error");
1455    }
1456
1457    #[test]
1458    fn test_error_collector_collect() {
1459        let collector = ErrorCollector::new();
1460
1461        let ok_result: Result<i32> = Ok(42);
1462        let err_result: Result<i32> = Err(anyhow::anyhow!("Test error"));
1463
1464        let ok_value = collector.collect(ok_result, Some("source1"));
1465        let err_value = collector.collect(err_result, Some("source2"));
1466
1467        assert_eq!(ok_value, Some(42));
1468        assert_eq!(err_value, None);
1469        assert_eq!(collector.error_count(), 1);
1470    }
1471
1472    #[test]
1473    fn test_resource_manager() {
1474        let rm = ResourceManager::new(100, 4); // 100 MB max
1475
1476        assert!(
1477            rm.try_allocate(50 * 1024 * 1024)
1478                .expect("Allocation failed")
1479        );
1480        assert_eq!(
1481            rm.current_usage_mb().expect("Usage check failed").round() as u64,
1482            50
1483        );
1484
1485        assert!(
1486            rm.try_allocate(50 * 1024 * 1024)
1487                .expect("Allocation failed")
1488        );
1489        assert!(
1490            !rm.try_allocate(1024 * 1024)
1491                .expect("Allocation check failed")
1492        );
1493
1494        rm.release(50 * 1024 * 1024).expect("Release failed");
1495        assert_eq!(
1496            rm.current_usage_mb().expect("Usage check failed").round() as u64,
1497            50
1498        );
1499    }
1500
1501    #[test]
1502    fn test_resource_manager_threads() {
1503        let rm = ResourceManager::new(100, 2);
1504
1505        assert!(rm.acquire_thread());
1506        assert!(rm.acquire_thread());
1507        assert!(!rm.acquire_thread()); // Should fail, max reached
1508
1509        rm.release_thread();
1510        assert!(rm.acquire_thread()); // Should succeed now
1511    }
1512
1513    #[test]
1514    fn test_batch_manager() {
1515        let bm = BatchManager::new(10);
1516        let items: Vec<i32> = (0..100).collect();
1517
1518        let result = bm
1519            .process_batches(items, Ok, "Test batch processing")
1520            .expect("Batch processing failed");
1521
1522        assert_eq!(result.len(), 100);
1523    }
1524
1525    #[test]
1526    fn test_tile_processor() {
1527        let processor = ParallelTileProcessor::new(256, 256);
1528
1529        let results = processor
1530            .process_tiles(1024, 1024, 0, |x, y, w, h| Ok((x, y, w, h)), "Test tiles")
1531            .expect("Tile processing failed");
1532
1533        assert_eq!(results.len(), 16); // 4x4 tiles
1534    }
1535
1536    #[test]
1537    fn test_pipeline_stage() {
1538        let stage = FnStage::new("double", |x: i32| Ok(x * 2));
1539
1540        let result = stage.process(21).expect("Processing failed");
1541        assert_eq!(result, 42);
1542        assert_eq!(stage.name(), "double");
1543    }
1544
1545    #[test]
1546    fn test_pipeline_executor() {
1547        let executor = PipelineExecutor::new();
1548
1549        let stage1 = FnStage::new("add_one", |x: i32| Ok(x + 1));
1550        let stage2 = FnStage::new("double", |x: i32| Ok(x * 2));
1551
1552        let inputs: Vec<i32> = (0..10).collect();
1553        let result = executor
1554            .execute_two_stage(inputs, &stage1, &stage2, "Test pipeline")
1555            .expect("Pipeline failed");
1556
1557        assert_eq!(result.successes.len(), 10);
1558        assert!(result.failures.is_empty());
1559        assert_eq!(result.successes[0], 2); // (0 + 1) * 2
1560        assert_eq!(result.successes[1], 4); // (1 + 1) * 2
1561    }
1562
1563    #[test]
1564    fn test_three_stage_pipeline() {
1565        let executor = PipelineExecutor::new();
1566
1567        let stage1 = FnStage::new("add_one", |x: i32| Ok(x + 1));
1568        let stage2 = FnStage::new("double", |x: i32| Ok(x * 2));
1569        let stage3 = FnStage::new("to_string", |x: i32| Ok(x.to_string()));
1570
1571        let inputs: Vec<i32> = (0..5).collect();
1572        let result = executor
1573            .execute_three_stage(inputs, &stage1, &stage2, &stage3, "Test 3-stage pipeline")
1574            .expect("Pipeline failed");
1575
1576        assert_eq!(result.successes.len(), 5);
1577        assert_eq!(result.successes[0], "2");
1578        assert_eq!(result.successes[1], "4");
1579    }
1580
1581    #[test]
1582    fn test_progress_aggregator() {
1583        let agg = ProgressAggregator::new();
1584
1585        agg.register_operation("op1", 100)
1586            .expect("Registration failed");
1587        agg.register_operation("op2", 50)
1588            .expect("Registration failed");
1589
1590        agg.update("op1", 10, 0).expect("Update failed");
1591        agg.update("op2", 5, 1).expect("Update failed");
1592
1593        let stats = agg.get_aggregate_stats().expect("Stats failed");
1594        assert_eq!(stats.total, 150);
1595        assert_eq!(stats.completed, 15);
1596        assert_eq!(stats.failed, 1);
1597    }
1598}