portalis_transpiler/wasi_threading/
thread.rs1use anyhow::{Result, Context, anyhow};
6use std::time::Duration;
7use super::ThreadingError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ThreadPriority {
12 Low,
14 Normal,
16 High,
18}
19
20#[derive(Debug, Clone)]
22pub struct ThreadConfig {
23 pub name: Option<String>,
25 pub stack_size: Option<usize>,
27 pub priority: ThreadPriority,
29}
30
31impl ThreadConfig {
32 pub fn new() -> Self {
34 Self {
35 name: None,
36 stack_size: None,
37 priority: ThreadPriority::Normal,
38 }
39 }
40
41 pub fn with_name(mut self, name: impl Into<String>) -> Self {
43 self.name = Some(name.into());
44 self
45 }
46
47 pub fn with_stack_size(mut self, size: usize) -> Self {
49 self.stack_size = Some(size);
50 self
51 }
52
53 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct ThreadId(u64);
69
70impl ThreadId {
71 pub fn new(id: u64) -> Self {
73 Self(id)
74 }
75
76 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
88pub 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 pub fn thread_id(&self) -> ThreadId {
105 self.thread_id
106 }
107
108 #[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 false
118 }
119
120 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 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 #[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 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 self.join()
174 }
175}
176
177pub struct WasiThread;
179
180impl WasiThread {
181 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 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 let result = Arc::new(parking_lot::Mutex::new(None));
223
224 *result.lock() = Some(f());
227
228 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 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 pub fn current_id() -> ThreadId {
269 current_thread_id()
270 }
271
272 #[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 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 }
298 }
299}
300
301pub 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) }
313}
314
315pub 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 let start = instant::Instant::now();
327 while start.elapsed() < duration {
328 thread_yield();
329 }
330 }
331}
332
333pub fn thread_yield() {
335 #[cfg(not(target_arch = "wasm32"))]
336 {
337 std::thread::yield_now();
338 }
339
340 #[cfg(target_arch = "wasm32")]
341 {
342 }
344}
345
346#[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 }
356
357#[cfg(not(target_arch = "wasm32"))]
359pub fn thread_unpark<T>(_handle: &ThreadHandle<T>) {
360 }
363
364#[cfg(target_arch = "wasm32")]
365pub fn thread_unpark<T>(_handle: &ThreadHandle<T>) {
366 }
368
369#[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#[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}