Support multi-threaded Wasm (#3236)

Replace `atomic_refcell` with `parking_lot` on wasm32.

`parking_lot` has had problems running on wasm32 before
(https://github.com/emilk/egui/issues/1401)
but it works these days.
If we have problems again we can always switch to `std::sync::Mutex`.

Closes https://github.com/emilk/egui/issues/3102
This commit is contained in:
Emil Ernerfeldt 2023-08-11 15:08:00 +02:00 committed by GitHub
parent 08fb447fb5
commit dd5285cccb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 19 additions and 98 deletions

7
Cargo.lock generated
View File

@ -326,12 +326,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "atomic_refcell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31"
[[package]]
name = "atspi"
version = "0.10.1"
@ -1394,7 +1388,6 @@ version = "0.22.0"
dependencies = [
"ab_glyph",
"ahash 0.8.3",
"atomic_refcell",
"backtrace",
"bytemuck",
"criterion",

View File

@ -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"] }
@ -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]

View File

@ -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>);
@ -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>);
@ -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`].
@ -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>);
@ -148,7 +153,6 @@ mod rw_lock_impl {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "deadlock_detection")]
mod rw_lock_impl {
use std::{
@ -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>,
@ -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};
@ -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));