memscope_rs/async_memory/
error.rs

1//! Error handling for async memory tracking
2//!
3//! Provides a unified error type following the project's error handling patterns.
4//! All errors are designed to be recoverable and provide meaningful context.
5
6use std::fmt;
7use std::sync::Arc;
8
9/// Unified error type for async memory tracking operations
10///
11/// Follows the project's pattern of using `Arc<str>` for efficient error messages
12/// and avoiding string cloning overhead.
13#[derive(Debug, Clone)]
14pub enum AsyncError {
15    /// Task tracking initialization or configuration errors
16    Initialization {
17        component: Arc<str>,
18        message: Arc<str>,
19        recoverable: bool,
20    },
21
22    /// Task identification and propagation errors
23    TaskTracking {
24        operation: TaskOperation,
25        message: Arc<str>,
26        task_id: Option<crate::async_memory::TaskId>,
27    },
28
29    /// Memory allocation tracking errors
30    AllocationTracking {
31        event_type: AllocationEventType,
32        message: Arc<str>,
33        allocation_size: Option<usize>,
34    },
35
36    /// Event buffer management errors
37    BufferManagement {
38        buffer_type: BufferType,
39        message: Arc<str>,
40        events_lost: Option<usize>,
41    },
42
43    /// Data aggregation and analysis errors
44    DataAggregation {
45        aggregator: Arc<str>,
46        message: Arc<str>,
47        partial_data_available: bool,
48    },
49
50    /// Integration errors with tokio runtime or tracing
51    Integration {
52        component: Arc<str>,
53        message: Arc<str>,
54        fallback_available: bool,
55    },
56
57    /// System-level errors (threading, IO, etc.)
58    System {
59        operation: Arc<str>,
60        message: Arc<str>,
61        source_error: Option<Arc<str>>,
62    },
63}
64
65/// Task tracking operation types
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum TaskOperation {
68    /// Task ID generation from Context
69    IdGeneration,
70    /// Task information propagation via thread-local storage
71    Propagation,
72    /// Task registration in tracking system
73    Registration,
74    /// Task completion cleanup
75    Cleanup,
76}
77
78/// Allocation event types for error context
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum AllocationEventType {
81    /// Memory allocation event
82    Allocation,
83    /// Memory deallocation event
84    Deallocation,
85    /// Event buffer write operation
86    BufferWrite,
87    /// Event processing by aggregator
88    Processing,
89}
90
91/// Buffer management operation types
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum BufferType {
94    /// Per-thread allocation event buffer
95    AllocationEvents,
96    /// Task profile cache
97    TaskProfiles,
98    /// Quality metrics buffer
99    QualityMetrics,
100}
101
102impl AsyncError {
103    /// Create an initialization error
104    pub fn initialization(component: &str, message: &str, recoverable: bool) -> Self {
105        Self::Initialization {
106            component: Arc::from(component),
107            message: Arc::from(message),
108            recoverable,
109        }
110    }
111
112    /// Create a task tracking error
113    pub fn task_tracking(
114        operation: TaskOperation,
115        message: &str,
116        task_id: Option<crate::async_memory::TaskId>,
117    ) -> Self {
118        Self::TaskTracking {
119            operation,
120            message: Arc::from(message),
121            task_id,
122        }
123    }
124
125    /// Create an allocation tracking error
126    pub fn allocation_tracking(
127        event_type: AllocationEventType,
128        message: &str,
129        allocation_size: Option<usize>,
130    ) -> Self {
131        Self::AllocationTracking {
132            event_type,
133            message: Arc::from(message),
134            allocation_size,
135        }
136    }
137
138    /// Create a buffer management error
139    pub fn buffer_management(
140        buffer_type: BufferType,
141        message: &str,
142        events_lost: Option<usize>,
143    ) -> Self {
144        Self::BufferManagement {
145            buffer_type,
146            message: Arc::from(message),
147            events_lost,
148        }
149    }
150
151    /// Create a data aggregation error
152    pub fn data_aggregation(aggregator: &str, message: &str, partial_data: bool) -> Self {
153        Self::DataAggregation {
154            aggregator: Arc::from(aggregator),
155            message: Arc::from(message),
156            partial_data_available: partial_data,
157        }
158    }
159
160    /// Create an integration error
161    pub fn integration(component: &str, message: &str, fallback_available: bool) -> Self {
162        Self::Integration {
163            component: Arc::from(component),
164            message: Arc::from(message),
165            fallback_available,
166        }
167    }
168
169    /// Create a system error
170    pub fn system(operation: &str, message: &str, source: Option<&str>) -> Self {
171        Self::System {
172            operation: Arc::from(operation),
173            message: Arc::from(message),
174            source_error: source.map(Arc::from),
175        }
176    }
177
178    /// Check if this error is recoverable
179    pub fn is_recoverable(&self) -> bool {
180        match self {
181            Self::Initialization { recoverable, .. } => *recoverable,
182            Self::TaskTracking { .. } => true, // Task errors are usually recoverable
183            Self::AllocationTracking { .. } => true, // Allocation errors don't crash system
184            Self::BufferManagement { .. } => true, // Buffer overflow is expected
185            Self::DataAggregation {
186                partial_data_available,
187                ..
188            } => *partial_data_available,
189            Self::Integration {
190                fallback_available, ..
191            } => *fallback_available,
192            Self::System { .. } => false, // System errors are typically fatal
193        }
194    }
195
196    /// Get the primary component affected by this error
197    pub fn component(&self) -> &str {
198        match self {
199            Self::Initialization { component, .. } => component,
200            Self::TaskTracking { .. } => "task_tracking",
201            Self::AllocationTracking { .. } => "allocation_tracking",
202            Self::BufferManagement { .. } => "buffer_management",
203            Self::DataAggregation { aggregator, .. } => aggregator,
204            Self::Integration { component, .. } => component,
205            Self::System { operation, .. } => operation,
206        }
207    }
208
209    /// Get the error message
210    pub fn message(&self) -> &str {
211        match self {
212            Self::Initialization { message, .. }
213            | Self::TaskTracking { message, .. }
214            | Self::AllocationTracking { message, .. }
215            | Self::BufferManagement { message, .. }
216            | Self::DataAggregation { message, .. }
217            | Self::Integration { message, .. }
218            | Self::System { message, .. } => message,
219        }
220    }
221}
222
223impl fmt::Display for AsyncError {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self {
226            Self::Initialization {
227                component,
228                message,
229                recoverable,
230            } => {
231                write!(
232                    f,
233                    "Async memory tracking initialization failed in {}: {} ({})",
234                    component,
235                    message,
236                    if *recoverable { "recoverable" } else { "fatal" }
237                )
238            }
239            Self::TaskTracking {
240                operation,
241                message,
242                task_id,
243            } => {
244                if let Some(id) = task_id {
245                    write!(
246                        f,
247                        "Task tracking error during {:?} for task {}: {}",
248                        operation, id, message
249                    )
250                } else {
251                    write!(f, "Task tracking error during {:?}: {}", operation, message)
252                }
253            }
254            Self::AllocationTracking {
255                event_type,
256                message,
257                allocation_size,
258            } => {
259                if let Some(size) = allocation_size {
260                    write!(
261                        f,
262                        "Allocation tracking error during {:?} ({}B): {}",
263                        event_type, size, message
264                    )
265                } else {
266                    write!(
267                        f,
268                        "Allocation tracking error during {:?}: {}",
269                        event_type, message
270                    )
271                }
272            }
273            Self::BufferManagement {
274                buffer_type,
275                message,
276                events_lost,
277            } => {
278                if let Some(lost) = events_lost {
279                    write!(
280                        f,
281                        "Buffer management error in {:?} ({} events lost): {}",
282                        buffer_type, lost, message
283                    )
284                } else {
285                    write!(
286                        f,
287                        "Buffer management error in {:?}: {}",
288                        buffer_type, message
289                    )
290                }
291            }
292            Self::DataAggregation {
293                aggregator,
294                message,
295                partial_data_available,
296            } => {
297                write!(
298                    f,
299                    "Data aggregation error in {}: {} (partial data: {})",
300                    aggregator, message, partial_data_available
301                )
302            }
303            Self::Integration {
304                component,
305                message,
306                fallback_available,
307            } => {
308                write!(
309                    f,
310                    "Integration error with {}: {} (fallback: {})",
311                    component,
312                    message,
313                    if *fallback_available {
314                        "available"
315                    } else {
316                        "unavailable"
317                    }
318                )
319            }
320            Self::System {
321                operation,
322                message,
323                source_error,
324            } => {
325                if let Some(source) = source_error {
326                    write!(
327                        f,
328                        "System error during {}: {} (source: {})",
329                        operation, message, source
330                    )
331                } else {
332                    write!(f, "System error during {}: {}", operation, message)
333                }
334            }
335        }
336    }
337}
338
339impl std::error::Error for AsyncError {
340    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
341        // No source errors for now, but could be extended
342        None
343    }
344}
345
346/// Result type alias for async memory tracking operations
347pub type AsyncResult<T> = Result<T, AsyncError>;
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use std::error::Error;
353
354    #[test]
355    fn test_error_creation() {
356        let error = AsyncError::initialization("tracker", "Failed to start", true);
357        assert!(error.is_recoverable());
358        assert_eq!(error.component(), "tracker");
359        assert_eq!(error.message(), "Failed to start");
360    }
361
362    #[test]
363    fn test_error_display() {
364        let error =
365            AsyncError::task_tracking(TaskOperation::IdGeneration, "Invalid context", Some(12345));
366        let display = format!("{}", error);
367        assert!(display.contains("IdGeneration"));
368        assert!(display.contains("12345"));
369        assert!(display.contains("Invalid context"));
370    }
371
372    #[test]
373    fn test_buffer_error_with_events_lost() {
374        let error = AsyncError::buffer_management(
375            BufferType::AllocationEvents,
376            "Buffer overflow",
377            Some(42),
378        );
379        let display = format!("{}", error);
380        assert!(display.contains("42 events lost"));
381    }
382
383    #[test]
384    fn test_error_recoverability() {
385        // Recoverable errors
386        assert!(
387            AsyncError::task_tracking(TaskOperation::Propagation, "test", None).is_recoverable()
388        );
389        assert!(
390            AsyncError::allocation_tracking(AllocationEventType::Allocation, "test", None)
391                .is_recoverable()
392        );
393
394        // Non-recoverable errors
395        assert!(!AsyncError::system("io", "disk failure", None).is_recoverable());
396    }
397
398    #[test]
399    fn test_initialization_error_non_recoverable() {
400        let error = AsyncError::initialization("runtime", "Critical failure", false);
401        assert!(!error.is_recoverable());
402        assert_eq!(error.component(), "runtime");
403        assert_eq!(error.message(), "Critical failure");
404
405        let display = format!("{}", error);
406        assert!(display.contains("fatal"));
407        assert!(display.contains("runtime"));
408        assert!(display.contains("Critical failure"));
409    }
410
411    #[test]
412    fn test_task_tracking_all_operations() {
413        let operations = [
414            TaskOperation::IdGeneration,
415            TaskOperation::Propagation,
416            TaskOperation::Registration,
417            TaskOperation::Cleanup,
418        ];
419
420        for op in &operations {
421            let error = AsyncError::task_tracking(*op, "test message", None);
422            assert!(error.is_recoverable());
423            assert_eq!(error.component(), "task_tracking");
424
425            let display = format!("{}", error);
426            assert!(display.contains(&format!("{:?}", op)));
427        }
428    }
429
430    #[test]
431    fn test_task_tracking_without_task_id() {
432        let error = AsyncError::task_tracking(TaskOperation::Registration, "No task ID", None);
433        let display = format!("{}", error);
434        assert!(display.contains("Registration"));
435        assert!(display.contains("No task ID"));
436        // When no task_id is provided, the format should not include "for task [id]"
437        assert!(!display.contains("for task"));
438    }
439
440    #[test]
441    fn test_allocation_tracking_all_event_types() {
442        let event_types = [
443            AllocationEventType::Allocation,
444            AllocationEventType::Deallocation,
445            AllocationEventType::BufferWrite,
446            AllocationEventType::Processing,
447        ];
448
449        for event_type in &event_types {
450            let error = AsyncError::allocation_tracking(*event_type, "test", Some(1024));
451            assert!(error.is_recoverable());
452            assert_eq!(error.component(), "allocation_tracking");
453
454            let display = format!("{}", error);
455            assert!(display.contains(&format!("{:?}", event_type)));
456            assert!(display.contains("1024B"));
457        }
458    }
459
460    #[test]
461    fn test_allocation_tracking_without_size() {
462        let error =
463            AsyncError::allocation_tracking(AllocationEventType::Processing, "Unknown size", None);
464        let display = format!("{}", error);
465        assert!(display.contains("Processing"));
466        assert!(display.contains("Unknown size"));
467        assert!(!display.contains("B):"));
468    }
469
470    #[test]
471    fn test_buffer_management_all_types() {
472        let buffer_types = [
473            BufferType::AllocationEvents,
474            BufferType::TaskProfiles,
475            BufferType::QualityMetrics,
476        ];
477
478        for buffer_type in &buffer_types {
479            let error = AsyncError::buffer_management(*buffer_type, "test error", None);
480            assert!(error.is_recoverable());
481            assert_eq!(error.component(), "buffer_management");
482
483            let display = format!("{}", error);
484            assert!(display.contains(&format!("{:?}", buffer_type)));
485        }
486    }
487
488    #[test]
489    fn test_buffer_management_without_events_lost() {
490        let error =
491            AsyncError::buffer_management(BufferType::TaskProfiles, "Generic buffer error", None);
492        let display = format!("{}", error);
493        assert!(display.contains("TaskProfiles"));
494        assert!(display.contains("Generic buffer error"));
495        assert!(!display.contains("events lost"));
496    }
497
498    #[test]
499    fn test_data_aggregation_with_partial_data() {
500        let error = AsyncError::data_aggregation("metrics_collector", "Incomplete data", true);
501        assert!(error.is_recoverable());
502        assert_eq!(error.component(), "metrics_collector");
503        assert_eq!(error.message(), "Incomplete data");
504
505        let display = format!("{}", error);
506        assert!(display.contains("metrics_collector"));
507        assert!(display.contains("partial data: true"));
508    }
509
510    #[test]
511    fn test_data_aggregation_without_partial_data() {
512        let error = AsyncError::data_aggregation("failed_aggregator", "Total failure", false);
513        assert!(!error.is_recoverable());
514        assert_eq!(error.component(), "failed_aggregator");
515
516        let display = format!("{}", error);
517        assert!(display.contains("partial data: false"));
518    }
519
520    #[test]
521    fn test_integration_with_fallback() {
522        let error = AsyncError::integration("tokio", "Runtime unavailable", true);
523        assert!(error.is_recoverable());
524        assert_eq!(error.component(), "tokio");
525
526        let display = format!("{}", error);
527        assert!(display.contains("tokio"));
528        assert!(display.contains("fallback: available"));
529    }
530
531    #[test]
532    fn test_integration_without_fallback() {
533        let error = AsyncError::integration("tracing", "Critical dependency missing", false);
534        assert!(!error.is_recoverable());
535        assert_eq!(error.component(), "tracing");
536
537        let display = format!("{}", error);
538        assert!(display.contains("fallback: unavailable"));
539    }
540
541    #[test]
542    fn test_system_error_with_source() {
543        let error = AsyncError::system("file_io", "Write failed", Some("Permission denied"));
544        assert!(!error.is_recoverable());
545        assert_eq!(error.component(), "file_io");
546        assert_eq!(error.message(), "Write failed");
547
548        let display = format!("{}", error);
549        assert!(display.contains("file_io"));
550        assert!(display.contains("Write failed"));
551        assert!(display.contains("source: Permission denied"));
552    }
553
554    #[test]
555    fn test_system_error_without_source() {
556        let error = AsyncError::system("network", "Connection timeout", None);
557        assert!(!error.is_recoverable());
558
559        let display = format!("{}", error);
560        assert!(display.contains("network"));
561        assert!(display.contains("Connection timeout"));
562        assert!(!display.contains("source:"));
563    }
564
565    #[test]
566    fn test_enum_equality() {
567        // Test TaskOperation equality
568        assert_eq!(TaskOperation::IdGeneration, TaskOperation::IdGeneration);
569        assert_ne!(TaskOperation::IdGeneration, TaskOperation::Propagation);
570
571        // Test AllocationEventType equality
572        assert_eq!(
573            AllocationEventType::Allocation,
574            AllocationEventType::Allocation
575        );
576        assert_ne!(
577            AllocationEventType::Allocation,
578            AllocationEventType::Deallocation
579        );
580
581        // Test BufferType equality
582        assert_eq!(BufferType::AllocationEvents, BufferType::AllocationEvents);
583        assert_ne!(BufferType::AllocationEvents, BufferType::TaskProfiles);
584    }
585
586    #[test]
587    fn test_enum_debug_formatting() {
588        // Test TaskOperation debug format
589        let op = TaskOperation::Registration;
590        assert_eq!(format!("{:?}", op), "Registration");
591
592        // Test AllocationEventType debug format
593        let event = AllocationEventType::BufferWrite;
594        assert_eq!(format!("{:?}", event), "BufferWrite");
595
596        // Test BufferType debug format
597        let buffer = BufferType::QualityMetrics;
598        assert_eq!(format!("{:?}", buffer), "QualityMetrics");
599    }
600
601    #[test]
602    fn test_error_clone() {
603        let original = AsyncError::initialization("test", "clone test", true);
604        let cloned = original.clone();
605
606        assert_eq!(original.component(), cloned.component());
607        assert_eq!(original.message(), cloned.message());
608        assert_eq!(original.is_recoverable(), cloned.is_recoverable());
609    }
610
611    #[test]
612    fn test_error_debug_formatting() {
613        let error = AsyncError::task_tracking(TaskOperation::Cleanup, "debug test", Some(999));
614        let debug_str = format!("{:?}", error);
615
616        assert!(debug_str.contains("TaskTracking"));
617        assert!(debug_str.contains("Cleanup"));
618        assert!(debug_str.contains("debug test"));
619        assert!(debug_str.contains("999"));
620    }
621
622    #[test]
623    fn test_error_source_method() {
624        let error = AsyncError::system("test", "source test", None);
625        assert!(error.source().is_none());
626    }
627
628    #[test]
629    fn test_async_result_type_alias() {
630        let success: AsyncResult<i32> = Ok(42);
631        assert!(success.is_ok());
632        if let Ok(value) = success {
633            assert_eq!(value, 42);
634        }
635
636        let failure: AsyncResult<i32> = Err(AsyncError::system("test", "fail", None));
637        assert!(failure.is_err());
638    }
639
640    #[test]
641    fn test_comprehensive_error_scenarios() {
642        // Test edge case combinations that might occur in real usage
643
644        // Task tracking with large task ID
645        let large_id_error = AsyncError::task_tracking(
646            TaskOperation::IdGeneration,
647            "Large ID test",
648            Some(u128::MAX),
649        );
650        let display = format!("{}", large_id_error);
651        assert!(display.contains(&u128::MAX.to_string()));
652
653        // Allocation tracking with zero size
654        let zero_size_error = AsyncError::allocation_tracking(
655            AllocationEventType::Allocation,
656            "Zero size allocation",
657            Some(0),
658        );
659        let display = format!("{}", zero_size_error);
660        assert!(display.contains("0B"));
661
662        // Buffer management with large events lost
663        let large_lost_error = AsyncError::buffer_management(
664            BufferType::AllocationEvents,
665            "Massive overflow",
666            Some(1_000_000),
667        );
668        let display = format!("{}", large_lost_error);
669        assert!(display.contains("1000000 events lost"));
670    }
671}