From 9eb6fc098aadc2b5d9f9d98dc66c94ce16919e79 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 4c7c68942ed9d4d2e2df0971b0f541269afae7e8..b0412b7d993181c478c0e6877791301c22168fe8 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)); + } } } }));