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.
This commit is contained in:
morgankrey authored and GitHub committed 2026-02-14 13:55:13 -06:00
1 parent 8c1ee9f2a3
commit b69bbc2e0e
1 file changed
+16 -10
+16 -10
View File
@@ -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));
}
}
}
}));