Skip to content

Commit

Permalink
Support multi-threaded Wasm (#3236)
Browse files Browse the repository at this point in the history
Replace `atomic_refcell` with `parking_lot` on wasm32.

`parking_lot` has had problems running on wasm32 before
(#1401)
but it works these days.
If we have problems again we can always switch to `std::sync::Mutex`.

Closes #3102
  • Loading branch information
emilk authored Aug 11, 2023
1 parent 08fb447 commit dd5285c
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 98 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions crates/epaint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ahash = { version = "0.8.1", default-features = false, features = [
"std",
] }
nohash-hasher = "0.2"
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.

#! ### Optional dependencies
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
Expand All @@ -94,11 +95,6 @@ serde = { version = "1", optional = true, features = ["derive", "rc"] }
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
backtrace = { version = "0.3", optional = true }
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.

# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401


[dev-dependencies]
Expand Down
104 changes: 18 additions & 86 deletions crates/epaint/src/mutex.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! Helper module that wraps some Mutex types with different implementations.
//! Helper module that adds extra checks when the `deadlock_detection` feature is turned on.
// ----------------------------------------------------------------------------

#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(debug_assertions))]
#[cfg(not(feature = "deadlock_detection"))]
mod mutex_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::Mutex`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>);

Expand All @@ -27,12 +28,13 @@ mod mutex_impl {
}
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(debug_assertions)]
#[cfg(feature = "deadlock_detection")]
mod mutex_impl {
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::Mutex`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>);

Expand Down Expand Up @@ -115,7 +117,8 @@ mod mutex_impl {
}
}

#[cfg(not(target_arch = "wasm32"))]
// ----------------------------------------------------------------------------

#[cfg(not(feature = "deadlock_detection"))]
mod rw_lock_impl {
/// The lock you get from [`RwLock::read`].
Expand All @@ -126,7 +129,9 @@ mod rw_lock_impl {

/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::RwLock`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct RwLock<T>(parking_lot::RwLock<T>);

Expand All @@ -148,7 +153,6 @@ mod rw_lock_impl {
}
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "deadlock_detection")]
mod rw_lock_impl {
use std::{
Expand Down Expand Up @@ -246,7 +250,9 @@ mod rw_lock_impl {

/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
/// This is a thin wrapper around [`parking_lot::RwLock`], except if
/// the feature `deadlock_detection` is turned enabled, in which case
/// extra checks are added to detect deadlocks.
#[derive(Default)]
pub struct RwLock<T> {
lock: parking_lot::RwLock<T>,
Expand Down Expand Up @@ -352,80 +358,6 @@ mod rw_lock_impl {

// ----------------------------------------------------------------------------

#[cfg(target_arch = "wasm32")]
mod mutex_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value

/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
#[derive(Default)]
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);

/// The lock you get from [`Mutex`].
pub use atomic_refcell::AtomicRefMut as MutexGuard;

impl<T> Mutex<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(atomic_refcell::AtomicRefCell::new(val))
}

/// Panics if already locked.
#[inline(always)]
pub fn lock(&self) -> MutexGuard<'_, T> {
self.0.borrow_mut()
}

#[inline(always)]
pub fn into_inner(self) -> T {
self.0.into_inner()
}
}
}

#[cfg(target_arch = "wasm32")]
mod rw_lock_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value

/// The lock you get from [`RwLock::read`].
pub use atomic_refcell::AtomicRef as RwLockReadGuard;

/// The lock you get from [`RwLock::write`].
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;

/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
#[derive(Default)]
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);

impl<T> RwLock<T> {
#[inline(always)]
pub fn new(val: T) -> Self {
Self(atomic_refcell::AtomicRefCell::new(val))
}

#[inline(always)]
pub fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.borrow()
}

/// Panics if already locked.
#[inline(always)]
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.borrow_mut()
}

#[inline(always)]
pub fn into_inner(self) -> T {
self.0.into_inner()
}
}
}

// ----------------------------------------------------------------------------

pub use mutex_impl::{Mutex, MutexGuard};
pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard};

Expand Down Expand Up @@ -469,7 +401,7 @@ mod tests {
let other_thread = {
let one = Arc::clone(&one);
std::thread::spawn(move || {
let _ = one.lock();
let _lock = one.lock();
})
};
std::thread::sleep(Duration::from_millis(200));
Expand Down

0 comments on commit dd5285c

Please sign in to comment.