Crates.io | wasm_safe_mutex |
lib.rs | wasm_safe_mutex |
version | 0.1.0 |
created_at | 2025-08-18 03:23:53.830982+00 |
updated_at | 2025-08-18 03:23:53.830982+00 |
description | A mutex safe for use in browsers on the main thread |
homepage | https://sealedabstract.com/code/wasm_safe_mutex |
repository | https://github.com/drewcrawford/wasm_safe_mutex |
max_upload_size | |
id | 1799906 |
size | 155,987 |
A WebAssembly-safe mutex that papers over platform-specific locking constraints.
WebAssembly's main thread cannot use blocking locks - attempting to do so will panic with "cannot block on the main thread". This is a fundamental limitation of the browser environment where blocking the main thread would freeze the entire UI.
However, blocking locks ARE allowed in:
Atomics.wait
is available)This crate papers over all these platform differences by automatically adapting its locking strategy based on the runtime environment:
thread::park
)Atomics.wait
when availableThis means you can write code once and have it work correctly across all platforms, without worrying about whether you're on the main thread, a worker thread, native or WASM.
Add this to your Cargo.toml
:
[dependencies]
wasm_safe_mutex = "0.1.0"
use wasm_safe_mutex::Mutex;
let mutex = Mutex::new(42);
let mut guard = mutex.lock_sync();
*guard = 100;
drop(guard);
// Value has been updated
let guard = mutex.lock_sync();
assert_eq!(*guard, 100);
use wasm_safe_mutex::{Mutex, NotAvailable};
let mutex = Mutex::new("data");
// First lock succeeds
let guard = mutex.try_lock().unwrap();
assert_eq!(*guard, "data");
// Second lock fails while first is held
let result = mutex.try_lock();
assert!(matches!(result, Err(NotAvailable)));
use wasm_safe_mutex::Mutex;
let mutex = Mutex::new(vec![1, 2, 3]);
// Async lock doesn't block the executor
let mut guard = mutex.lock_async().await;
guard.push(4);
drop(guard);
// Using the convenience method
let sum = mutex.with_async(|data| data.iter().sum::<i32>()).await;
assert_eq!(sum, 10);
use wasm_safe_mutex::Mutex;
use std::sync::Arc;
use std::thread;
let mutex = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..4)
.map(|_| {
let mutex = Arc::clone(&mutex);
thread::spawn(move || {
for _ in 0..25 {
mutex.with_mut_sync(|value| *value += 1);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(*mutex.lock_sync(), 100);
The Mutex<T>
type provides multiple locking strategies:
try_lock()
: Non-blocking attempt to acquire the locklock_spin()
: Spin-wait until the lock is acquiredlock_block()
: Blocks on native/WASM workers, spins on WASM main threadlock_sync()
: Automatically chooses the right strategy for your platform (recommended)lock_async()
: Always non-blocking, works everywhere including WASM main threadwith_sync()
: Execute a read-only closure with the lockwith_mut_sync()
: Execute a mutable closure with the lockwith_async()
: Execute a read-only closure with the lock asynchronouslywith_mut_async()
: Execute a mutable closure with the lock asynchronouslyThe mutex transparently handles platform differences:
Atomics.wait
This automatic adaptation means your code works everywhere without modification.