Add single-threaded deadlock detection to RwMutex (#1619)
This commit is contained in:
parent
810b609a80
commit
d6fd5dec3b
6 changed files with 93 additions and 3 deletions
|
@ -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
generated
1
Cargo.lock
generated
|
@ -1352,6 +1352,7 @@ dependencies = [
|
|||
"ab_glyph",
|
||||
"ahash 0.7.6",
|
||||
"atomic_refcell",
|
||||
"backtrace",
|
||||
"bytemuck",
|
||||
"cint",
|
||||
"color-hex",
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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…
Reference in a new issue