diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 03249335377230c7c24310c76cb31409cc26744f..cc4eaee492618812f1ee361d549b5e0052dafc68 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -894,6 +894,9 @@ pub(crate) struct HandleId { /// created, all participating strong entities in this cycle will effectively /// leak as they cannot be released anymore. /// +/// Cycles can also happen if an entity owns a task or subscription that it +/// itself owns a strong reference to the entity again. +/// /// # Usage /// /// You can use `WeakEntity::assert_released` or `AnyWeakEntity::assert_released` @@ -919,7 +922,7 @@ pub(crate) struct HandleId { /// ``` /// /// This will capture and display backtraces for each leaked handle, helping you -/// identify where handles were created but not released. +/// identify where leaked handles were created. /// /// # How It Works /// @@ -1002,11 +1005,13 @@ impl LeakDetector { /// otherwise it suggests setting the environment variable to get more info. pub fn assert_released(&mut self, entity_id: EntityId) { use std::fmt::Write as _; + if let Some(data) = self.entity_handles.remove(&entity_id) { let mut out = String::new(); for (_, backtrace) in data.handles { if let Some(mut backtrace) = backtrace { backtrace.resolve(); + let backtrace = BacktraceFormatter(backtrace); writeln!(out, "Leaked handle:\n{:?}", backtrace).unwrap(); } else { writeln!( @@ -1054,6 +1059,7 @@ impl LeakDetector { if let Some(backtrace) = backtrace { let mut backtrace = backtrace.clone(); backtrace.resolve(); + let backtrace = BacktraceFormatter(backtrace); writeln!( out, "Leaked handle for entity {} ({entity_id:?}):\n{:?}", @@ -1091,6 +1097,7 @@ impl Drop for LeakDetector { for (_handle, backtrace) in data.handles { if let Some(mut backtrace) = backtrace { backtrace.resolve(); + let backtrace = BacktraceFormatter(backtrace); writeln!( out, "Leaked handle for entity {} ({entity_id:?}):\n{:?}", @@ -1111,6 +1118,71 @@ impl Drop for LeakDetector { } } +#[cfg(any(test, feature = "leak-detection"))] +struct BacktraceFormatter(backtrace::Backtrace); + +#[cfg(any(test, feature = "leak-detection"))] +impl fmt::Debug for BacktraceFormatter { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + use backtrace::{BacktraceFmt, BytesOrWideString, PrintFmt}; + + let style = if fmt.alternate() { + PrintFmt::Full + } else { + PrintFmt::Short + }; + + // When printing paths we try to strip the cwd if it exists, otherwise + // we just print the path as-is. Note that we also only do this for the + // short format, because if it's full we presumably want to print + // everything. + let cwd = std::env::current_dir(); + let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| { + let path = path.into_path_buf(); + if style != PrintFmt::Full { + if let Ok(cwd) = &cwd { + if let Ok(suffix) = path.strip_prefix(cwd) { + return fmt::Display::fmt(&suffix.display(), fmt); + } + } + } + fmt::Display::fmt(&path.display(), fmt) + }; + + let mut f = BacktraceFmt::new(fmt, style, &mut print_path); + f.add_context()?; + let mut strip = true; + for frame in self.0.frames() { + if let [symbol, ..] = frame.symbols() + && let Some(name) = symbol.name() + && let Some(filename) = name.as_str() + { + match filename { + "test::run_test_in_process" + | "scheduler::executor::spawn_local_with_source_location::impl$1::poll > > > >,alloc::alloc::Global> > >" => { + strip = true + } + "gpui::app::entity_map::LeakDetector::handle_created" => { + strip = false; + continue; + } + "zed::main" => { + strip = true; + f.frame().backtrace_frame(frame)?; + } + _ => {} + } + } + if strip { + continue; + } + f.frame().backtrace_frame(frame)?; + } + f.finish()?; + Ok(()) + } +} + #[cfg(test)] mod test { use crate::EntityMap; diff --git a/crates/gpui_windows/src/platform.rs b/crates/gpui_windows/src/platform.rs index 182107138579be858272329a75a9daededd438e4..7e9f1e77487b4185fbad9e1dc66cfcb1c8191e61 100644 --- a/crates/gpui_windows/src/platform.rs +++ b/crates/gpui_windows/src/platform.rs @@ -1326,7 +1326,15 @@ unsafe extern "system" fn window_procedure( } let inner = unsafe { &*ptr }; let result = if let Some(inner) = inner.upgrade() { - inner.handle_msg(hwnd, msg, wparam, lparam) + if cfg!(debug_assertions) { + let inner = std::panic::AssertUnwindSafe(inner); + match std::panic::catch_unwind(|| { inner }.handle_msg(hwnd, msg, wparam, lparam)) { + Ok(result) => result, + Err(_) => std::process::abort(), + } + } else { + inner.handle_msg(hwnd, msg, wparam, lparam) + } } else { unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 7f732481309ca7f4d7e91c7d70bc1b529f6a2e28..8fefed0cefbe6316e66073acd6bf681bc8fcbf9c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -387,7 +387,7 @@ pub fn initialize_workspace( } cx.spawn(async move |cx| { cx.background_executor() - .timer(Duration::from_millis(501)) + .timer(std::time::Duration::from_millis(1500)) .await; multi_workspace_handle.assert_released();