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 `*_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)).
|
* 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)).
|
* 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)).
|
* Added `InputState::stable_dt`: a more stable estimate for the delta-time in reactive mode ([#1625](https://github.com/emilk/egui/pull/1625)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1352,6 +1352,7 @@ dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
|
"backtrace",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cint",
|
"cint",
|
||||||
"color-hex",
|
"color-hex",
|
||||||
|
|
|
@ -28,6 +28,11 @@ cint = ["epaint/cint"]
|
||||||
# implement bytemuck on most types.
|
# implement bytemuck on most types.
|
||||||
bytemuck = ["epaint/bytemuck"]
|
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 set, egui will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
default_fonts = ["epaint/default_fonts"]
|
default_fonts = ["epaint/default_fonts"]
|
||||||
|
|
|
@ -3,8 +3,9 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## 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)).
|
* 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
|
## 0.18.1 - 2022-05-01
|
||||||
|
|
|
@ -32,6 +32,11 @@ default = ["default_fonts"]
|
||||||
# implement bytemuck on most types.
|
# implement bytemuck on most types.
|
||||||
bytemuck = ["dep:bytemuck", "emath/bytemuck"]
|
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 set, epaint will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
default_fonts = []
|
default_fonts = []
|
||||||
|
@ -51,17 +56,18 @@ serde = ["dep:serde", "ahash/serde", "emath/serde"]
|
||||||
emath = { version = "0.18.0", path = "../emath" }
|
emath = { version = "0.18.0", path = "../emath" }
|
||||||
|
|
||||||
ab_glyph = "0.2.11"
|
ab_glyph = "0.2.11"
|
||||||
|
ahash = { version = "0.7", default-features = false, features = ["std"] }
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
|
|
||||||
# Optional:
|
# 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"] }
|
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||||
cint = { version = "0.3.1", optional = true }
|
cint = { version = "0.3.1", optional = true }
|
||||||
|
color-hex = { version = "0.2.0", optional = true }
|
||||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||||
|
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[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.
|
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||||
|
|
||||||
# web:
|
# web:
|
||||||
|
|
|
@ -111,6 +111,7 @@ mod mutex_impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(feature = "deadlock_detection"))]
|
||||||
mod rw_lock_impl {
|
mod rw_lock_impl {
|
||||||
/// The lock you get from [`RwLock::read`].
|
/// The lock you get from [`RwLock::read`].
|
||||||
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
|
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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|
Loading…
Reference in a new issue