From b69bbc2e0e9e2db6b51b55f4bbe543bf6205df32 Mon Sep 17 00:00:00 2001 From: morgankrey Date: Sat, 14 Feb 2026 13:55:13 -0600 Subject: [PATCH] gpui: Fix RefCell panic in thermal/keyboard state callbacks (#49187) The `on_thermal_state_change` and `on_keyboard_layout_change` callbacks in `App::new_app()` called `borrow_mut()` unconditionally. These callbacks are invoked asynchronously by macOS via dispatch queues when the system's thermal or keyboard state changes, which can happen while the app's RefCell is already borrowed during an update cycle, causing a panic. This change uses `try_borrow_mut()` instead. If the borrow fails (because the app is already borrowed), the callback silently skips the update - the state can be queried on the next frame. Fixes [ZED-4WM](https://zed-dev.sentry.io/issues/ZED-4WM) Closes #49181 Release Notes: - Fixed a crash on macOS caused by thermal or keyboard layout state changes occurring during UI updates. --- crates/gpui/src/app.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4c7c68942e..b0412b7d99 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -732,12 +732,15 @@ impl App { let app = Rc::downgrade(&app); move || { if let Some(app) = app.upgrade() { - let cx = &mut app.borrow_mut(); - cx.keyboard_layout = cx.platform.keyboard_layout(); - cx.keyboard_mapper = cx.platform.keyboard_mapper(); - cx.keyboard_layout_observers - .clone() - .retain(&(), move |callback| (callback)(cx)); + // Use try_borrow_mut because this callback can be called asynchronously + // from macOS while the app is already borrowed during an update. + if let Ok(mut cx) = app.try_borrow_mut() { + cx.keyboard_layout = cx.platform.keyboard_layout(); + cx.keyboard_mapper = cx.platform.keyboard_mapper(); + cx.keyboard_layout_observers + .clone() + .retain(&(), move |callback| (callback)(&mut cx)); + } } } })); @@ -746,10 +749,13 @@ impl App { let app = Rc::downgrade(&app); move || { if let Some(app) = app.upgrade() { - let cx = &mut app.borrow_mut(); - cx.thermal_state_observers - .clone() - .retain(&(), move |callback| (callback)(cx)); + // Use try_borrow_mut because this callback can be called asynchronously + // from macOS while the app is already borrowed during an update. + if let Ok(mut cx) = app.try_borrow_mut() { + cx.thermal_state_observers + .clone() + .retain(&(), move |callback| (callback)(&mut cx)); + } } } }));