Skip to main content

memscope_rs/
utils.rs

1//! Common utility functions shared across modules
2
3use std::hash::{Hash, Hasher};
4use std::thread::ThreadId;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7/// Get current timestamp in nanoseconds since Unix epoch
8pub fn current_timestamp_nanos() -> u64 {
9    SystemTime::now()
10        .duration_since(UNIX_EPOCH)
11        .unwrap_or_default()
12        .as_nanos() as u64
13}
14
15/// Convert ThreadId to u64 for serialization and storage.
16///
17/// This uses a hash-based approach since ThreadId cannot be directly
18/// converted to u64. The hash is consistent for the same ThreadId.
19pub fn thread_id_to_u64(thread_id: ThreadId) -> u64 {
20    let mut hasher = std::collections::hash_map::DefaultHasher::new();
21    thread_id.hash(&mut hasher);
22    hasher.finish()
23}
24
25/// Capture-local thread identity, safe even inside allocator callbacks during
26/// Rust thread bootstrap. Calling `std::thread::current()` there can initialize
27/// runtime TLS before `spawn` installs its thread handle and abort the process.
28/// These monotonically assigned IDs are not OS CPU IDs or hashed Rust ThreadIds.
29pub fn current_thread_id_u64() -> u64 {
30    static NEXT_THREAD_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
31    thread_local! {
32        static CAPTURE_THREAD_ID: u64 = NEXT_THREAD_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
33    }
34    CAPTURE_THREAD_ID.with(|id| *id)
35}
36
37/// Format bytes in a human-readable format
38pub fn format_bytes(bytes: usize) -> String {
39    const KB: usize = 1024;
40    const MB: usize = KB * 1024;
41    const GB: usize = MB * 1024;
42    const TB: usize = GB * 1024;
43    const PB: usize = TB * 1024;
44
45    if bytes < KB {
46        format!("{bytes}B")
47    } else if bytes < MB {
48        format!("{:.1}KB", bytes as f64 / KB as f64)
49    } else if bytes < GB {
50        format!("{:.1}MB", bytes as f64 / MB as f64)
51    } else if bytes < TB {
52        format!("{:.1}GB", bytes as f64 / GB as f64)
53    } else if bytes < PB {
54        format!("{:.1}TB", bytes as f64 / TB as f64)
55    } else {
56        format!("{:.1}PB", bytes as f64 / PB as f64)
57    }
58}
59
60/// Simplify Rust type names for better readability - Enhanced Unknown Type identification
61pub fn simplify_type_name(type_name: &str) -> (String, String) {
62    // Handle empty or explicitly unknown types first
63    if type_name.is_empty() || type_name == "Unknown" {
64        return ("Unknown Type".to_string(), "Unknown".to_string());
65    }
66
67    // Clean up the type name - remove extra whitespace and normalize
68    let clean_type = type_name.trim();
69
70    // Enhanced pattern matching with more comprehensive coverage
71    if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
72        let inner = extract_generic_type(clean_type, "Vec");
73        (format!("Vec<{inner}>"), "Collections".to_string())
74    } else if clean_type.contains("HashMap") || clean_type.contains("hash_map") {
75        ("HashMap<K,V>".to_string(), "Collections".to_string())
76    } else if clean_type.contains("BTreeMap") || clean_type.contains("btree_map") {
77        ("BTreeMap<K,V>".to_string(), "Collections".to_string())
78    } else if clean_type.contains("BTreeSet") || clean_type.contains("btree_set") {
79        ("BTreeSet<T>".to_string(), "Collections".to_string())
80    } else if clean_type.contains("HashSet") || clean_type.contains("hash_set") {
81        ("HashSet<T>".to_string(), "Collections".to_string())
82    } else if clean_type.contains("VecDeque") || clean_type.contains("vec_deque") {
83        ("VecDeque<T>".to_string(), "Collections".to_string())
84    } else if clean_type.contains("Box<") || clean_type.contains("boxed::Box") {
85        let inner = extract_generic_type(clean_type, "Box");
86        if inner.contains("String") || inner.contains("string::String") {
87            ("String".to_string(), "Basic Types".to_string())
88        } else if inner.contains("HashMap") || inner.contains("hash_map") {
89            ("HashMap<K,V>".to_string(), "Collections".to_string())
90        } else if inner.contains("BTreeMap") || inner.contains("btree_map") {
91            ("BTreeMap<K,V>".to_string(), "Collections".to_string())
92        } else if inner.contains("BTreeSet") || inner.contains("btree_set") {
93            ("BTreeSet<T>".to_string(), "Collections".to_string())
94        } else if inner.contains("HashSet") || inner.contains("hash_set") {
95            ("HashSet<T>".to_string(), "Collections".to_string())
96        } else if inner.contains("VecDeque") || inner.contains("vec_deque") {
97            ("VecDeque<T>".to_string(), "Collections".to_string())
98        } else if inner.contains("Vec") || inner.contains("vec::Vec") {
99            let vec_inner = extract_generic_type(&inner, "Vec");
100            (format!("Vec<{vec_inner}>"), "Collections".to_string())
101        } else {
102            (format!("Box<{inner}>"), "Smart Pointers".to_string())
103        }
104    } else if clean_type.contains("Rc<") || clean_type.contains("rc::Rc") {
105        let inner = extract_generic_type(clean_type, "Rc");
106        if inner.contains("String") || inner.contains("string::String") {
107            ("String".to_string(), "Basic Types".to_string())
108        } else {
109            (format!("Rc<{inner}>"), "Smart Pointers".to_string())
110        }
111    } else if clean_type.contains("Arc<") || clean_type.contains("sync::Arc") {
112        let inner = extract_generic_type(clean_type, "Arc");
113        if inner.contains("String") || inner.contains("string::String") {
114            ("String".to_string(), "Basic Types".to_string())
115        } else {
116            (format!("Arc<{inner}>"), "Smart Pointers".to_string())
117        }
118    } else if clean_type.contains("String") || clean_type.contains("string::String") {
119        ("String".to_string(), "Basic Types".to_string())
120    } else if clean_type.contains("LinkedList") {
121        ("LinkedList<T>".to_string(), "Collections".to_string())
122    } else if clean_type.contains("&str") || clean_type == "str" {
123        ("&str".to_string(), "Basic Types".to_string())
124    } else if clean_type.contains("CString") || clean_type.contains("CStr") {
125        ("CString".to_string(), "Basic Types".to_string())
126    } else if clean_type.contains("OsString") || clean_type.contains("OsStr") {
127        ("OsString".to_string(), "Basic Types".to_string())
128    } else if clean_type.contains("PathBuf") || clean_type.contains("Path") {
129        ("PathBuf".to_string(), "Basic Types".to_string())
130    } else if clean_type.contains("Option<") {
131        ("Option<T>".to_string(), "Optionals".to_string())
132    } else if clean_type.contains("Result<") {
133        ("Result<T,E>".to_string(), "Results".to_string())
134    } else if clean_type.matches("i32").count() > 0
135        || clean_type.matches("u32").count() > 0
136        || clean_type.matches("i64").count() > 0
137        || clean_type.matches("u64").count() > 0
138        || clean_type.matches("f64").count() > 0
139        || clean_type.matches("f32").count() > 0
140        || clean_type.matches("i8").count() > 0
141        || clean_type.matches("u8").count() > 0
142        || clean_type.matches("i16").count() > 0
143        || clean_type.matches("u16").count() > 0
144        || clean_type.matches("isize").count() > 0
145        || clean_type.matches("usize").count() > 0
146        || clean_type.matches("bool").count() > 0
147        || clean_type.matches("char").count() > 0
148    {
149        let primitive = clean_type.split("::").last().unwrap_or(clean_type);
150        (primitive.to_string(), "Basic Types".to_string())
151    } else if clean_type.contains("[") && clean_type.contains("]") {
152        ("Array".to_string(), "Arrays".to_string())
153    } else if clean_type.starts_with("(") && clean_type.ends_with(")") {
154        ("Tuple".to_string(), "Tuples".to_string())
155    } else if clean_type.contains("Mutex<") || clean_type.contains("RwLock<") {
156        ("Mutex/RwLock".to_string(), "Synchronization".to_string())
157    } else if clean_type.contains("Cell<") || clean_type.contains("RefCell<") {
158        (
159            "Cell/RefCell".to_string(),
160            "Interior Mutability".to_string(),
161        )
162    } else if clean_type.contains("Weak<") {
163        ("Weak<T>".to_string(), "Smart Pointers".to_string())
164    } else if clean_type.starts_with("std::")
165        || clean_type.starts_with("alloc::")
166        || clean_type.starts_with("core::")
167    {
168        let simplified = clean_type.split("::").last().unwrap_or(clean_type);
169        (simplified.to_string(), "Standard Library".to_string())
170    } else if clean_type.contains("::") {
171        // Handle custom types with namespaces
172        let parts: Vec<&str> = clean_type.split("::").collect();
173        if parts.len() >= 2 {
174            // Use the original clean_type as fallback if parts is somehow empty
175            let type_part = parts.last().unwrap_or(&clean_type);
176
177            // Determine category based on namespace and type patterns
178            let category = if parts
179                .iter()
180                .any(|&part| part.contains("error") || part.contains("Error"))
181                || type_part.ends_with("Error")
182                || type_part.contains("Err")
183            {
184                "Error Types"
185            } else if type_part.ends_with("Config") || type_part.ends_with("Settings") {
186                "Configuration"
187            } else if type_part.ends_with("Builder") || type_part.ends_with("Factory") {
188                "Builders"
189            } else if parts
190                .iter()
191                .any(|&part| part == "std" || part == "core" || part == "alloc")
192            {
193                "Standard Library"
194            } else if parts
195                .iter()
196                .any(|&part| part.contains("test") || part.contains("mock"))
197            {
198                "Test Types"
199            } else if parts.len() > 2 {
200                // Deep namespace suggests library or framework type
201                "Library Types"
202            } else {
203                "Custom Types"
204            };
205
206            (type_part.to_string(), category.to_string())
207        } else {
208            // Single part, no namespace
209            let category = if clean_type.ends_with("Error") || clean_type.contains("Err") {
210                "Error Types"
211            } else if clean_type.ends_with("Config") || clean_type.ends_with("Settings") {
212                "Configuration"
213            } else if clean_type.ends_with("Builder") || clean_type.ends_with("Factory") {
214                "Builders"
215            } else if clean_type.chars().next().is_some_and(|c| c.is_uppercase()) {
216                // Starts with uppercase, likely a struct/enum
217                "Custom Types"
218            } else {
219                // Lowercase, might be a function type or other
220                "Other Types"
221            };
222
223            (clean_type.to_string(), category.to_string())
224        }
225    } else {
226        // For simple type names without namespace
227        if !clean_type.is_empty() {
228            (clean_type.to_string(), "Custom Types".to_string())
229        } else {
230            ("Unknown Type".to_string(), "Unknown".to_string())
231        }
232    }
233}
234
235/// Extract generic type parameter for display
236pub fn extract_generic_type(type_name: &str, container: &str) -> String {
237    if let Some(start) = type_name.find(&format!("{container}<")) {
238        let content_start = start + container.len() + 1;
239        let content = &type_name[content_start..];
240
241        let mut depth = 1;
242        let mut end = 0;
243
244        for (i, c) in content.char_indices() {
245            match c {
246                '<' => depth += 1,
247                '>' => {
248                    depth -= 1;
249                    if depth == 0 {
250                        end = i;
251                        break;
252                    }
253                }
254                _ => {}
255            }
256        }
257
258        if end > 0 {
259            let inner = &content[..end];
260            return inner.split("::").last().unwrap_or(inner).to_string();
261        }
262    }
263    "?".to_string()
264}
265
266/// Get a simplified type name for display
267pub fn get_simple_type(type_name: &str) -> String {
268    if type_name.contains("String") {
269        "String".to_string()
270    } else if type_name.contains("Vec") {
271        "Vec".to_string()
272    } else if type_name.contains("Box") {
273        "Box".to_string()
274    } else if type_name.contains("Rc") {
275        "Rc".to_string()
276    } else if type_name.contains("Arc") {
277        "Arc".to_string()
278    } else if type_name.contains("HashMap") {
279        "HashMap".to_string()
280    } else {
281        type_name
282            .split("::")
283            .last()
284            .unwrap_or("Unknown")
285            .to_string()
286    }
287}
288
289/// Get color for category - Enhanced with new categories
290pub fn get_category_color(category: &str) -> String {
291    match category {
292        "Collections" => "#3498db".to_string(),               // Blue
293        "Basic Types" => "#27ae60".to_string(),               // Green for Basic Types
294        "Strings" => "#27ae60".to_string(),                   // Green (legacy support)
295        "Text" => "#27ae60".to_string(),                      // Green (legacy support)
296        "Smart Pointers" => "#e74c3c".to_string(),            // Red
297        "Reference Counted" => "#f39c12".to_string(),         // Orange
298        "Thread-Safe Shared" => "#9b59b6".to_string(),        // Purple
299        "Primitives" => "#1abc9c".to_string(),                // Teal
300        "Arrays" => "#34495e".to_string(),                    // Dark Gray
301        "Tuples" => "#16a085".to_string(),                    // Dark Teal
302        "Optionals" => "#8e44ad".to_string(),                 // Dark Purple
303        "Results" => "#d35400".to_string(),                   // Dark Orange
304        "Standard Library" => "#2980b9".to_string(),          // Dark Blue
305        "Custom Types" => "#c0392b".to_string(),              // Dark Red
306        "Synchronization" => "#e67e22".to_string(),           // Orange
307        "Interior Mutability" => "#95a5a6".to_string(),       // Light Gray
308        "Error Types" => "#e74c3c".to_string(),               // Red
309        "Configuration" => "#3498db".to_string(),             // Blue
310        "Builders" => "#9b59b6".to_string(),                  // Purple
311        "Runtime/System Allocation" => "#bdc3c7".to_string(), // Light Gray for system allocations
312        "Unknown" => "#bdc3c7".to_string(),                   // Light Gray (legacy support)
313        _ => "#7f8c8d".to_string(),                           // Medium Gray for other unknowns
314    }
315}
316
317/// Get type-specific gradient colors for enhanced visualization
318pub fn get_type_gradient_colors(type_name: &str) -> (&'static str, &'static str) {
319    match type_name {
320        "String" => ("#00BCD4", "#00ACC1"),  // Teal gradient
321        "Vec" => ("#2196F3", "#1976D2"),     // Blue gradient
322        "Box" => ("#F44336", "#D32F2F"),     // Red gradient
323        "HashMap" => ("#4CAF50", "#388E3C"), // Green gradient
324        "Rc" => ("#FF9800", "#F57C00"),      // Orange gradient
325        "Arc" => ("#9C27B0", "#7B1FA2"),     // Purple gradient
326        _ => ("#607D8B", "#455A64"),         // Blue-gray gradient for custom types
327    }
328}
329
330/// Get color based on type for consistent visualization
331pub fn get_type_color(type_name: &str) -> &'static str {
332    match type_name {
333        "String" => "#2ecc71",
334        "Vec" => "#3498db",
335        "Box" => "#e74c3c",
336        "HashMap" => "#f39c12",
337        "Rc" => "#9b59b6",
338        "Arc" => "#1abc9c",
339        _ => "#95a5a6",
340    }
341}
342
343/// Enhanced type hierarchy classification for treemap visualization
344#[derive(Debug, Clone)]
345pub struct TypeHierarchy {
346    /// Major category: Collections, Strings, Smart Pointers, etc.
347    pub major_category: String,
348    /// Sub category: Maps, Sequences, Owned, Shared, etc.
349    pub sub_category: String,
350    /// Specific type: HashMap, Vec, Box, etc.
351    pub specific_type: String,
352    /// Full type name: HashMap<String, i32>
353    pub full_type: String,
354}
355
356/// Get comprehensive type hierarchy for treemap visualization
357pub fn get_type_category_hierarchy(type_name: &str) -> TypeHierarchy {
358    // Handle empty or unknown types first
359    if type_name.is_empty() || type_name == "Unknown" {
360        return TypeHierarchy {
361            major_category: "Unknown".to_string(),
362            sub_category: "Unidentified".to_string(),
363            specific_type: "Unknown Type".to_string(),
364            full_type: type_name.to_string(),
365        };
366    }
367
368    // Collections
369    if type_name.contains("HashMap") || type_name.contains("hash::map") {
370        let inner = extract_generic_params(type_name, "HashMap");
371        TypeHierarchy {
372            major_category: "Collections".to_string(),
373            sub_category: "Maps".to_string(),
374            specific_type: "HashMap".to_string(),
375            full_type: if inner.is_empty() {
376                "HashMap".to_string()
377            } else {
378                format!("HashMap<{inner}>")
379            },
380        }
381    } else if type_name.contains("BTreeMap") || type_name.contains("btree::map") {
382        let inner = extract_generic_params(type_name, "BTreeMap");
383        TypeHierarchy {
384            major_category: "Collections".to_string(),
385            sub_category: "Maps".to_string(),
386            specific_type: "BTreeMap".to_string(),
387            full_type: if inner.is_empty() {
388                "BTreeMap".to_string()
389            } else {
390                format!("BTreeMap<{inner}>")
391            },
392        }
393    } else if type_name.contains("HashSet") || type_name.contains("hash::set") {
394        let inner = extract_generic_params(type_name, "HashSet");
395        TypeHierarchy {
396            major_category: "Collections".to_string(),
397            sub_category: "Sets".to_string(),
398            specific_type: "HashSet".to_string(),
399            full_type: if inner.is_empty() {
400                "HashSet".to_string()
401            } else {
402                format!("HashSet<{inner}>")
403            },
404        }
405    } else if type_name.contains("Vec") && !type_name.contains("VecDeque") {
406        let inner = extract_generic_params(type_name, "Vec");
407        TypeHierarchy {
408            major_category: "Collections".to_string(),
409            sub_category: "Sequences".to_string(),
410            specific_type: "Vec".to_string(),
411            full_type: if inner.is_empty() {
412                "Vec".to_string()
413            } else {
414                format!("Vec<{inner}>")
415            },
416        }
417    } else if type_name.contains("VecDeque") {
418        let inner = extract_generic_params(type_name, "VecDeque");
419        TypeHierarchy {
420            major_category: "Collections".to_string(),
421            sub_category: "Sequences".to_string(),
422            specific_type: "VecDeque".to_string(),
423            full_type: if inner.is_empty() {
424                "VecDeque".to_string()
425            } else {
426                format!("VecDeque<{inner}>")
427            },
428        }
429    }
430    // Strings
431    else if type_name.contains("String") && !type_name.contains("<") {
432        TypeHierarchy {
433            major_category: "Strings".to_string(),
434            sub_category: "Owned".to_string(),
435            specific_type: "String".to_string(),
436            full_type: "String".to_string(),
437        }
438    } else if type_name.contains("&str") || (type_name.contains("str") && type_name.contains("&")) {
439        TypeHierarchy {
440            major_category: "Strings".to_string(),
441            sub_category: "Borrowed".to_string(),
442            specific_type: "&str".to_string(),
443            full_type: "&str".to_string(),
444        }
445    }
446    // Smart Pointers
447    else if type_name.contains("Box<") {
448        let inner = extract_generic_params(type_name, "Box");
449        TypeHierarchy {
450            major_category: "Smart Pointers".to_string(),
451            sub_category: "Owned".to_string(),
452            specific_type: "Box".to_string(),
453            full_type: if inner.is_empty() {
454                "Box".to_string()
455            } else {
456                format!("Box<{inner}>")
457            },
458        }
459    } else if type_name.contains("Rc<") {
460        let inner = extract_generic_params(type_name, "Rc");
461        TypeHierarchy {
462            major_category: "Smart Pointers".to_string(),
463            sub_category: "Reference Counted".to_string(),
464            specific_type: "Rc".to_string(),
465            full_type: if inner.is_empty() {
466                "Rc".to_string()
467            } else {
468                format!("Rc<{inner}>")
469            },
470        }
471    } else if type_name.contains("Arc<") {
472        let inner = extract_generic_params(type_name, "Arc");
473        TypeHierarchy {
474            major_category: "Smart Pointers".to_string(),
475            sub_category: "Thread-Safe Shared".to_string(),
476            specific_type: "Arc".to_string(),
477            full_type: if inner.is_empty() {
478                "Arc".to_string()
479            } else {
480                format!("Arc<{inner}>")
481            },
482        }
483    }
484    // Primitives
485    else if is_primitive_type(type_name) {
486        let clean_type = type_name.split("::").last().unwrap_or(type_name);
487        let sub_cat = if clean_type.contains("i") || clean_type.contains("u") {
488            "Integers"
489        } else if clean_type.contains("f") {
490            "Floats"
491        } else if clean_type == "bool" {
492            "Boolean"
493        } else {
494            "Other"
495        };
496        TypeHierarchy {
497            major_category: "Primitives".to_string(),
498            sub_category: sub_cat.to_string(),
499            specific_type: clean_type.to_string(),
500            full_type: clean_type.to_string(),
501        }
502    }
503    // Fallback
504    else {
505        let simplified = type_name.split("::").last().unwrap_or(type_name);
506        TypeHierarchy {
507            major_category: "Custom Types".to_string(),
508            sub_category: "User Defined".to_string(),
509            specific_type: simplified.to_string(),
510            full_type: simplified.to_string(),
511        }
512    }
513}
514
515/// Extract generic parameters from type names (enhanced version)
516pub fn extract_generic_params(type_name: &str, container: &str) -> String {
517    if let Some(start) = type_name.find(&format!("{container}<")) {
518        let start = start + container.len() + 1;
519        if let Some(end) = find_matching_bracket(type_name, start - 1) {
520            let inner = &type_name[start..end];
521            // Simplify the inner type
522            return inner.split("::").last().unwrap_or(inner).to_string();
523        }
524    }
525    String::new()
526}
527
528/// Find matching closing bracket for generic types
529fn find_matching_bracket(s: &str, start: usize) -> Option<usize> {
530    let chars: Vec<char> = s.chars().collect();
531    if start >= chars.len() || chars[start] != '<' {
532        return None;
533    }
534
535    let mut depth = 1;
536    for (i, item) in chars.iter().enumerate().skip(start + 1) {
537        match item {
538            '<' => depth += 1,
539            '>' => {
540                depth -= 1;
541                if depth == 0 {
542                    return Some(i);
543                }
544            }
545            _ => {}
546        }
547    }
548    None
549}
550
551/// Check if a type is a primitive type
552pub fn is_primitive_type(type_name: &str) -> bool {
553    let clean_type = type_name.split("::").last().unwrap_or(type_name);
554    matches!(
555        clean_type,
556        "i8" | "i16"
557            | "i32"
558            | "i64"
559            | "i128"
560            | "isize"
561            | "u8"
562            | "u16"
563            | "u32"
564            | "u64"
565            | "u128"
566            | "usize"
567            | "f32"
568            | "f64"
569            | "bool"
570            | "char"
571    )
572}
573
574/// Extract array information for display
575pub fn extract_array_info(type_name: &str) -> String {
576    if let Some(start) = type_name.find('[') {
577        if let Some(end) = type_name.find(']') {
578            if end > start {
579                return type_name[start..=end].to_string();
580            }
581        }
582    }
583    "Array".to_string()
584}
585
586/// Extract standard library module name
587pub fn extract_std_module(type_name: &str) -> String {
588    let parts: Vec<&str> = type_name.split("::").collect();
589    if parts.len() >= 2 {
590        match parts[1] {
591            "collections" => "Collections".to_string(),
592            "sync" => "Synchronization".to_string(),
593            "thread" => "Threading".to_string(),
594            "fs" => "File System".to_string(),
595            "net" => "Networking".to_string(),
596            "io" => "Input/Output".to_string(),
597            _ => "Other".to_string(),
598        }
599    } else {
600        "Other".to_string()
601    }
602}
603
604// ============================================================================
605// Thread Utilities (merged from thread_utils.rs)
606// ============================================================================
607
608use std::thread;
609use std::time::Duration;
610
611/// Extension trait for JoinHandle to add timeout functionality
612pub trait JoinHandleExt<T> {
613    /// Join with a timeout, returning an error if the timeout is exceeded
614    ///
615    /// This implementation properly handles timeout by using a dedicated timeout thread
616    /// that will exit immediately, avoiding resource leaks. The original thread continues
617    /// running in the background if it doesn't finish within the timeout.
618    fn join_timeout(self, timeout: Duration) -> Result<T, Box<dyn std::any::Any + Send + 'static>>;
619}
620
621impl<T: Send + 'static> JoinHandleExt<T> for thread::JoinHandle<T> {
622    fn join_timeout(self, timeout: Duration) -> Result<T, Box<dyn std::any::Any + Send + 'static>> {
623        // If the thread is already finished, just join it
624        if self.is_finished() {
625            return self.join().map_err(|e| Box::new(e) as _);
626        }
627
628        // Use a channel to communicate result or timeout
629        let (tx, rx) = std::sync::mpsc::channel();
630
631        // Spawn a thread that will join the original thread and send the result
632        thread::spawn(move || {
633            let result = self.join();
634            // Ignore send errors - the receiver might have been dropped due to timeout
635            let _ = tx.send(result);
636        });
637
638        // Wait for either the thread to finish or timeout to occur
639        match rx.recv_timeout(timeout) {
640            Ok(result) => result.map_err(|e| Box::new(e) as _),
641            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
642                // Timeout occurred - the original thread continues running in background
643                // This is unavoidable in Rust without thread cancellation
644                Err(Box::new("Thread join timed out") as _)
645            }
646            Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
647                // Sender disconnected unexpectedly
648                Err(Box::new("Thread communication error") as _)
649            }
650        }
651    }
652}
653
654#[cfg(test)]
655mod tests;