From 2a56d74d684f82a1b4c6a1d787a7bf23c11ad573 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:31:21 +0000 Subject: [PATCH] gpui_tokio: Fix `child task panicked: JoinError::Cancelled` panics (#51995) (cherry-pick to preview) (#51997) Cherry-pick of #51995 to preview ---- This panic stemmed from us dropping the tokio runtime before dropping wasm tasks which then could attempt to spawn_blocking, immediately failing and panicking on unwrap inside wasmtime. Closes ZED-5DE Release Notes: - N/A or Added/Fixed/Improved ... Co-authored-by: Lukas Wirth --- crates/gpui/src/app.rs | 59 +++++++++++++++++++++---------- crates/gpui/src/app/entity_map.rs | 5 +-- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3d22d48a3a808a6f437a5875bfd4e337b7672d80..ea1e294973d65a0b83329a20c067626755de6444 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -579,21 +579,13 @@ impl GpuiMode { pub struct App { pub(crate) this: Weak, pub(crate) platform: Rc, - pub(crate) mode: GpuiMode, text_system: Arc, - flushing_effects: bool, - pending_updates: usize, + pub(crate) actions: Rc, pub(crate) active_drag: Option, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, - pub(crate) loading_assets: FxHashMap<(TypeId, u64), Box>, - asset_source: Arc, - pub(crate) svg_renderer: SvgRenderer, - http_client: Arc, - pub(crate) globals_by_type: FxHashMap>, pub(crate) entities: EntityMap, - pub(crate) window_update_stack: Vec, pub(crate) new_entity_observers: SubscriberSet, pub(crate) windows: SlotMap>>, pub(crate) window_handles: FxHashMap, @@ -604,10 +596,8 @@ pub struct App { pub(crate) global_action_listeners: FxHashMap>>, pending_effects: VecDeque, - pub(crate) pending_notifications: FxHashSet, - pub(crate) pending_global_notifications: FxHashSet, + pub(crate) observers: SubscriberSet, - // TypeId is the type of the event that the listener callback expects pub(crate) event_listeners: SubscriberSet, pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>, pub(crate) keystroke_interceptors: SubscriberSet<(), KeystrokeObserver>, @@ -617,8 +607,30 @@ pub struct App { pub(crate) global_observers: SubscriberSet, pub(crate) quit_observers: SubscriberSet<(), QuitHandler>, pub(crate) restart_observers: SubscriberSet<(), Handler>, - pub(crate) restart_path: Option, pub(crate) window_closed_observers: SubscriberSet<(), WindowClosedHandler>, + + /// Per-App element arena. This isolates element allocations between different + /// App instances (important for tests where multiple Apps run concurrently). + pub(crate) element_arena: RefCell, + /// Per-App event arena. + pub(crate) event_arena: Arena, + + // Drop globals last. We need to ensure all tasks owned by entities and + // callbacks are marked cancelled at this point as this will also shutdown + // the tokio runtime. As any task attempting to spawn a blocking tokio task, + // might panic. + pub(crate) globals_by_type: FxHashMap>, + + // assets + pub(crate) loading_assets: FxHashMap<(TypeId, u64), Box>, + asset_source: Arc, + pub(crate) svg_renderer: SvgRenderer, + http_client: Arc, + + // below is plain data, the drop order is insignificant here + pub(crate) pending_notifications: FxHashSet, + pub(crate) pending_global_notifications: FxHashSet, + pub(crate) restart_path: Option, pub(crate) layout_id_buffer: Vec, // We recycle this memory across layout requests. pub(crate) propagate_event: bool, pub(crate) prompt_builder: Option, @@ -632,13 +644,18 @@ pub struct App { #[cfg(any(test, feature = "test-support", debug_assertions))] pub(crate) name: Option<&'static str>, pub(crate) text_rendering_mode: Rc>, + + pub(crate) window_update_stack: Vec, + pub(crate) mode: GpuiMode, + flushing_effects: bool, + pending_updates: usize, quit_mode: QuitMode, quitting: bool, - /// Per-App element arena. This isolates element allocations between different - /// App instances (important for tests where multiple Apps run concurrently). - pub(crate) element_arena: RefCell, - /// Per-App event arena. - pub(crate) event_arena: Arena, + + // We need to ensure the leak detector drops last, after all tasks, callbacks and things have been dropped. + // Otherwise it may report false positives. + #[cfg(any(test, feature = "leak-detection"))] + _ref_counts: Arc>, } impl App { @@ -660,6 +677,9 @@ impl App { let keyboard_layout = platform.keyboard_layout(); let keyboard_mapper = platform.keyboard_mapper(); + #[cfg(any(test, feature = "leak-detection"))] + let _ref_counts = entities.ref_counts_drop_handle(); + let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { this: this.clone(), @@ -719,6 +739,9 @@ impl App { name: None, element_arena: RefCell::new(Arena::new(1024 * 1024)), event_arena: Arena::new(1024 * 1024), + + #[cfg(any(test, feature = "leak-detection"))] + _ref_counts, }), }); diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index c12f952cc82ae8c161c5263ea47533bdef55e5e5..766610aac9e8de619694662da9d4c62472a62b1d 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -59,7 +59,8 @@ pub(crate) struct EntityMap { ref_counts: Arc>, } -struct EntityRefCounts { +#[doc(hidden)] +pub(crate) struct EntityRefCounts { counts: SlotMap, dropped_entity_ids: Vec, #[cfg(any(test, feature = "leak-detection"))] @@ -84,7 +85,7 @@ impl EntityMap { } #[doc(hidden)] - pub fn ref_counts_drop_handle(&self) -> impl Sized + use<> { + pub fn ref_counts_drop_handle(&self) -> Arc> { self.ref_counts.clone() }