Browse Source

Add single-threaded deadlock detection to RwMutex (#1619)

pull/1658/head
Emil Ernerfeldt 3 years ago
committed by GitHub
parent
commit
d6fd5dec3b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 1
      Cargo.lock
  3. 5
      egui/Cargo.toml
  4. 3
      epaint/CHANGELOG.md
  5. 10
      epaint/Cargo.toml
  6. 76
      epaint/src/mutex.rs

1
CHANGELOG.md

@ -9,6 +9,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui-w
* Added `*_released` & `*_clicked` methods for `PointerState` ([#1582](https://github.com/emilk/egui/pull/1582)).
* Added `egui::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
* Optimized painting of filled circles (e.g. for scatter plots) by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
* Added `InputState::stable_dt`: a more stable estimate for the delta-time in reactive mode ([#1625](https://github.com/emilk/egui/pull/1625)).
### Fixed 🐛

1
Cargo.lock

@ -1352,6 +1352,7 @@ dependencies = [
"ab_glyph",
"ahash 0.7.6",
"atomic_refcell",
"backtrace",
"bytemuck",
"cint",
"color-hex",

5
egui/Cargo.toml

@ -28,6 +28,11 @@ cint = ["epaint/cint"]
# implement bytemuck on most types.
bytemuck = ["epaint/bytemuck"]
# This will automatically detect deadlocks due to double-locking on the same thread.
# If your app freezes, you may want to enable this!
# Only affects `epaint::mutex::RwLock` (which egui uses a lot).
deadlock_detection = ["epaint/deadlock_detection"]
# If set, egui will use `include_bytes!` to bundle some fonts.
# If you plan on specifying your own fonts you may disable this feature.
default_fonts = ["epaint/default_fonts"]

3
epaint/CHANGELOG.md

@ -3,8 +3,9 @@ All notable changes to the epaint crate will be documented in this file.
## Unreleased
* Optimize tessellation of filled circles by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
* Added `epaint::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
* Optimize tessellation of filled circles by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
## 0.18.1 - 2022-05-01

10
epaint/Cargo.toml

@ -32,6 +32,11 @@ default = ["default_fonts"]
# implement bytemuck on most types.
bytemuck = ["dep:bytemuck", "emath/bytemuck"]
# This will automatically detect deadlocks due to double-locking on the same thread.
# If your app freezes, you may want to enable this!
# Only affects `epaint::mutex::RwLock` (which epaint and egui uses a lot).
deadlock_detection = ["dep:backtrace"]
# If set, epaint will use `include_bytes!` to bundle some fonts.
# If you plan on specifying your own fonts you may disable this feature.
default_fonts = []
@ -51,17 +56,18 @@ serde = ["dep:serde", "ahash/serde", "emath/serde"]
emath = { version = "0.18.0", path = "../emath" }
ab_glyph = "0.2.11"
ahash = { version = "0.7", default-features = false, features = ["std"] }
nohash-hasher = "0.2"
# Optional:
color-hex = { version = "0.2.0", optional = true }
ahash = { version = "0.7", default-features = false, features = ["std"] }
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
cint = { version = "0.3.1", optional = true }
color-hex = { version = "0.2.0", optional = true }
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:

76
epaint/src/mutex.rs

@ -111,6 +111,7 @@ mod mutex_impl {
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "deadlock_detection"))]
mod rw_lock_impl {
/// The lock you get from [`RwLock::read`].
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
@ -142,6 +143,81 @@ mod rw_lock_impl {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "deadlock_detection")]
mod rw_lock_impl {
/// The lock you get from [`RwLock::read`].
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
/// The lock you get from [`RwLock::write`].
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
/// Provides interior mutability.
///
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
#[derive(Default)]
pub struct RwLock<T> {
lock: parking_lot::RwLock<T>,
last_lock: parking_lot::Mutex<backtrace::Backtrace>,
}
impl<T> RwLock<T> {
pub fn new(val: T) -> Self {
Self {
lock: parking_lot::RwLock::new(val),
last_lock: Default::default(),
}
}
pub fn read(&self) -> RwLockReadGuard<'_, T> {
if self.lock.is_locked_exclusive() {
panic!(
"{} DEAD-LOCK DETECTED! Previous lock held at:\n{}\n\n",
std::any::type_name::<Self>(),
format_backtrace(&mut self.last_lock.lock())
);
}
*self.last_lock.lock() = make_backtrace();
parking_lot::RwLockReadGuard::map(self.lock.read(), |v| v)
}
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
if self.lock.is_locked() {
panic!(
"{} DEAD-LOCK DETECTED! Previous lock held at:\n{}\n\n",
std::any::type_name::<Self>(),
format_backtrace(&mut self.last_lock.lock())
);
}
*self.last_lock.lock() = make_backtrace();
parking_lot::RwLockWriteGuard::map(self.lock.write(), |v| v)
}
}
fn make_backtrace() -> backtrace::Backtrace {
backtrace::Backtrace::new_unresolved()
}
fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String {
backtrace.resolve();
let stacktrace = format!("{:?}", backtrace);
// Remove irrelevant parts of the stacktrace:
let end_offset = stacktrace
.find("std::sys_common::backtrace::__rust_begin_short_backtrace")
.unwrap_or(stacktrace.len());
let stacktrace = &stacktrace[..end_offset];
let first_interesting_function = "epaint::mutex::rw_lock_impl::make_backtrace\n";
if let Some(start_offset) = stacktrace.find(first_interesting_function) {
stacktrace[start_offset + first_interesting_function.len()..].to_owned()
} else {
stacktrace.to_owned()
}
}
}
// ----------------------------------------------------------------------------
#[cfg(target_arch = "wasm32")]

Loading…
Cancel
Save