Skip to main content

portalis_transpiler/wasi_threading/
thread.rs

1//! Thread Primitives
2//!
3//! Provides thread creation, joining, and management across platforms.
4
5use anyhow::{Result, Context, anyhow};
6use std::time::Duration;
7use super::ThreadingError;
8
9/// Thread priority levels
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ThreadPriority {
12    /// Low priority (background tasks)
13    Low,
14    /// Normal priority (default)
15    Normal,
16    /// High priority (time-critical tasks)
17    High,
18}
19
20/// Thread configuration
21#[derive(Debug, Clone)]
22pub struct ThreadConfig {
23    /// Optional thread name for debugging
24    pub name: Option<String>,
25    /// Stack size in bytes (None = platform default)
26    pub stack_size: Option<usize>,
27    /// Thread priority
28    pub priority: ThreadPriority,
29}
30
31impl ThreadConfig {
32    /// Create a new thread configuration with default settings
33    pub fn new() -> Self {
34        Self {
35            name: None,
36            stack_size: None,
37            priority: ThreadPriority::Normal,
38        }
39    }
40
41    /// Set the thread name
42    pub fn with_name(mut self, name: impl Into<String>) -> Self {
43        self.name = Some(name.into());
44        self
45    }
46
47    /// Set the stack size
48    pub fn with_stack_size(mut self, size: usize) -> Self {
49        self.stack_size = Some(size);
50        self
51    }
52
53    /// Set the thread priority
54    pub fn with_priority(mut self, priority: ThreadPriority) -> Self {
55        self.priority = priority;
56        self
57    }
58}
59
60impl Default for ThreadConfig {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66/// Unique thread identifier
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct ThreadId(u64);
69
70impl ThreadId {
71    /// Create a new thread ID
72    pub fn new(id: u64) -> Self {
73        Self(id)
74    }
75
76    /// Get the underlying ID value
77    pub fn as_u64(&self) -> u64 {
78        self.0
79    }
80}
81
82impl std::fmt::Display for ThreadId {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "ThreadId({})", self.0)
85    }
86}
87
88/// Thread handle for joining and managing spawned threads
89pub struct ThreadHandle<T> {
90    #[cfg(not(target_arch = "wasm32"))]
91    inner: Option<std::thread::JoinHandle<T>>,
92
93    #[cfg(all(target_arch = "wasm32", feature = "wasm"))]
94    inner: Arc<parking_lot::Mutex<Option<T>>>,
95
96    #[cfg(all(target_arch = "wasm32", feature = "wasi"))]
97    inner: Option<std::thread::JoinHandle<T>>,
98
99    thread_id: ThreadId,
100}
101
102impl<T> ThreadHandle<T> {
103    /// Get the thread ID
104    pub fn thread_id(&self) -> ThreadId {
105        self.thread_id
106    }
107
108    /// Check if the thread has finished
109    #[cfg(not(target_arch = "wasm32"))]
110    pub fn is_finished(&self) -> bool {
111        self.inner.as_ref().map(|h| h.is_finished()).unwrap_or(true)
112    }
113
114    #[cfg(target_arch = "wasm32")]
115    pub fn is_finished(&self) -> bool {
116        // WASM doesn't support checking thread status
117        false
118    }
119
120    /// Wait for the thread to finish and return its result
121    pub fn join(mut self) -> Result<T> {
122        #[cfg(not(target_arch = "wasm32"))]
123        {
124            let handle = self.inner.take()
125                .ok_or_else(|| anyhow!(ThreadingError::Join("Thread already joined".to_string())))?;
126
127            handle.join()
128                .map_err(|e| anyhow!(ThreadingError::Panic(format!("{:?}", e))))
129        }
130
131        #[cfg(all(target_arch = "wasm32", feature = "wasm"))]
132        {
133            // Web Workers: poll for result
134            let result = self.inner.lock().take()
135                .ok_or_else(|| anyhow!(ThreadingError::Join("Thread result not available".to_string())))?;
136            Ok(result)
137        }
138
139        #[cfg(all(target_arch = "wasm32", feature = "wasi"))]
140        {
141            let handle = self.inner.take()
142                .ok_or_else(|| anyhow!(ThreadingError::Join("Thread already joined".to_string())))?;
143
144            handle.join()
145                .map_err(|e| anyhow!(ThreadingError::Panic(format!("{:?}", e))))
146        }
147    }
148
149    /// Wait for the thread to finish with a timeout
150    #[cfg(not(target_arch = "wasm32"))]
151    pub fn join_timeout(self, timeout: Duration) -> Result<T>
152    where
153        T: Send + 'static,
154    {
155        use std::sync::mpsc;
156
157        let (tx, rx) = mpsc::channel();
158        let thread_id = self.thread_id();
159
160        // Spawn a monitoring thread
161        std::thread::spawn(move || {
162            let result = self.join();
163            let _ = tx.send(result);
164        });
165
166        rx.recv_timeout(timeout)
167            .map_err(|_| anyhow!(ThreadingError::Timeout(format!("Thread {:?} did not finish in time", thread_id))))?
168    }
169
170    #[cfg(target_arch = "wasm32")]
171    pub fn join_timeout(self, _timeout: Duration) -> Result<T> {
172        // Fallback to regular join on WASM
173        self.join()
174    }
175}
176
177/// Main thread API
178pub struct WasiThread;
179
180impl WasiThread {
181    /// Spawn a new thread with default configuration
182    pub fn spawn<F, T>(f: F) -> Result<ThreadHandle<T>>
183    where
184        F: FnOnce() -> T + Send + 'static,
185        T: Send + 'static,
186    {
187        Self::spawn_with_config(f, ThreadConfig::default())
188    }
189
190    /// Spawn a new thread with custom configuration
191    pub fn spawn_with_config<F, T>(f: F, config: ThreadConfig) -> Result<ThreadHandle<T>>
192    where
193        F: FnOnce() -> T + Send + 'static,
194        T: Send + 'static,
195    {
196        #[cfg(not(target_arch = "wasm32"))]
197        {
198            let mut builder = std::thread::Builder::new();
199
200            if let Some(name) = config.name {
201                builder = builder.name(name);
202            }
203
204            if let Some(size) = config.stack_size {
205                builder = builder.stack_size(size);
206            }
207
208            let handle = builder.spawn(f)
209                .context("Failed to spawn thread")?;
210
211            let thread_id = ThreadId::new(hash_thread_id(&handle.thread().id()));
212
213            Ok(ThreadHandle {
214                inner: Some(handle),
215                thread_id,
216            })
217        }
218
219        #[cfg(all(target_arch = "wasm32", feature = "wasm"))]
220        {
221            // Browser: Use Web Workers (simplified for now)
222            let result = Arc::new(parking_lot::Mutex::new(None));
223
224            // In a real implementation, this would use Web Workers API
225            // For now, we execute immediately (no true parallelism in browser without Workers)
226            *result.lock() = Some(f());
227
228            // Generate a random thread ID
229            use std::collections::hash_map::RandomState;
230            use std::hash::{BuildHasher, Hash, Hasher};
231            let random_state = RandomState::new();
232            let mut hasher = random_state.build_hasher();
233            std::time::SystemTime::now().hash(&mut hasher);
234            let thread_id = ThreadId::new(hasher.finish());
235
236            Ok(ThreadHandle {
237                inner: result,
238                thread_id,
239            })
240        }
241
242        #[cfg(all(target_arch = "wasm32", feature = "wasi"))]
243        {
244            // WASI: May support wasi-threads in the future
245            let mut builder = std::thread::Builder::new();
246
247            if let Some(name) = config.name {
248                builder = builder.name(name);
249            }
250
251            if let Some(size) = config.stack_size {
252                builder = builder.stack_size(size);
253            }
254
255            let handle = builder.spawn(f)
256                .context("Failed to spawn thread")?;
257
258            let thread_id = ThreadId::new(hash_thread_id(&handle.thread().id()));
259
260            Ok(ThreadHandle {
261                inner: Some(handle),
262                thread_id,
263            })
264        }
265    }
266
267    /// Get the current thread's ID
268    pub fn current_id() -> ThreadId {
269        current_thread_id()
270    }
271
272    /// Get the current thread's name
273    #[cfg(not(target_arch = "wasm32"))]
274    pub fn current_name() -> Option<String> {
275        std::thread::current()
276            .name()
277            .map(|s| s.to_string())
278    }
279
280    #[cfg(target_arch = "wasm32")]
281    pub fn current_name() -> Option<String> {
282        None
283    }
284
285    /// Get available parallelism (number of CPUs)
286    pub fn available_parallelism() -> usize {
287        #[cfg(not(target_arch = "wasm32"))]
288        {
289            std::thread::available_parallelism()
290                .map(|n| n.get())
291                .unwrap_or(1)
292        }
293
294        #[cfg(target_arch = "wasm32")]
295        {
296            1 // WASM is single-threaded by default
297        }
298    }
299}
300
301/// Get the current thread ID
302pub fn current_thread_id() -> ThreadId {
303    #[cfg(not(target_arch = "wasm32"))]
304    {
305        let id = std::thread::current().id();
306        ThreadId::new(hash_thread_id(&id))
307    }
308
309    #[cfg(target_arch = "wasm32")]
310    {
311        ThreadId::new(1) // Main thread in WASM
312    }
313}
314
315/// Sleep for the specified duration
316pub fn thread_sleep(duration: Duration) {
317    #[cfg(not(target_arch = "wasm32"))]
318    {
319        std::thread::sleep(duration);
320    }
321
322    #[cfg(target_arch = "wasm32")]
323    {
324        // WASM: Blocking sleep not available, but we can yield
325        // In a real implementation, this would use async sleep
326        let start = instant::Instant::now();
327        while start.elapsed() < duration {
328            thread_yield();
329        }
330    }
331}
332
333/// Yield the current thread
334pub fn thread_yield() {
335    #[cfg(not(target_arch = "wasm32"))]
336    {
337        std::thread::yield_now();
338    }
339
340    #[cfg(target_arch = "wasm32")]
341    {
342        // WASM: No true yield, but we can do nothing
343    }
344}
345
346/// Park the current thread
347#[cfg(not(target_arch = "wasm32"))]
348pub fn thread_park() {
349    std::thread::park();
350}
351
352#[cfg(target_arch = "wasm32")]
353pub fn thread_park() {
354    // WASM: Not supported
355}
356
357/// Unpark a thread by its handle
358#[cfg(not(target_arch = "wasm32"))]
359pub fn thread_unpark<T>(_handle: &ThreadHandle<T>) {
360    // Note: This is a simplified API
361    // In real implementation, we'd need to store Thread objects
362}
363
364#[cfg(target_arch = "wasm32")]
365pub fn thread_unpark<T>(_handle: &ThreadHandle<T>) {
366    // WASM: Not supported
367}
368
369/// Hash a thread ID to u64
370#[cfg(not(target_arch = "wasm32"))]
371fn hash_thread_id(id: &std::thread::ThreadId) -> u64 {
372    use std::collections::hash_map::DefaultHasher;
373    use std::hash::{Hash, Hasher};
374
375    let mut hasher = DefaultHasher::new();
376    id.hash(&mut hasher);
377    hasher.finish()
378}
379
380/// Thread-local storage (native only)
381#[cfg(not(target_arch = "wasm32"))]
382#[macro_export]
383macro_rules! thread_local {
384    ($name:ident: $ty:ty = $init:expr) => {
385        thread_local!(static $name: std::cell::RefCell<$ty> = std::cell::RefCell::new($init));
386    };
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    #[cfg(not(target_arch = "wasm32"))]
395    fn test_thread_spawn() {
396        let handle = WasiThread::spawn(|| {
397            42
398        }).unwrap();
399
400        let result = handle.join().unwrap();
401        assert_eq!(result, 42);
402    }
403
404    #[test]
405    #[cfg(not(target_arch = "wasm32"))]
406    fn test_thread_config() {
407        let config = ThreadConfig::new()
408            .with_name("test-thread")
409            .with_stack_size(2 * 1024 * 1024)
410            .with_priority(ThreadPriority::High);
411
412        let handle = WasiThread::spawn_with_config(|| {
413            WasiThread::current_name()
414        }, config).unwrap();
415
416        let name = handle.join().unwrap();
417        assert_eq!(name.as_deref(), Some("test-thread"));
418    }
419
420    #[test]
421    fn test_thread_id() {
422        let id1 = current_thread_id();
423        let id2 = current_thread_id();
424        assert_eq!(id1, id2);
425    }
426
427    #[test]
428    fn test_sleep() {
429        let start = std::time::Instant::now();
430        thread_sleep(Duration::from_millis(50));
431        let elapsed = start.elapsed();
432        assert!(elapsed >= Duration::from_millis(50));
433    }
434
435    #[test]
436    #[cfg(not(target_arch = "wasm32"))]
437    fn test_available_parallelism() {
438        let count = WasiThread::available_parallelism();
439        assert!(count >= 1);
440    }
441}