diff --git a/crates/gpui/.rules b/crates/gpui/.rules new file mode 100644 index 0000000000000000000000000000000000000000..2f8b309cddc5480598be470996df5dd14b61d0c0 --- /dev/null +++ b/crates/gpui/.rules @@ -0,0 +1,9 @@ +# GPUI crate-specific rules +# +# This file is non-exhaustive. Check the root .rules for general guidelines. + +# Platform callbacks + +* Platform callbacks (e.g., `on_keyboard_layout_change`, `on_thermal_state_change`) can fire + asynchronously from macOS while `AppCell` is already borrowed. Defer work via + `ForegroundExecutor::spawn` rather than borrowing `AppCell` directly in the callback body. diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b0412b7d993181c478c0e6877791301c22168fe8..df66e74f9f19d04dfce2eb299155f57a1d832dba 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -654,6 +654,7 @@ impl App { ) -> Rc { let background_executor = platform.background_executor(); let foreground_executor = platform.foreground_executor(); + let callback_executor = foreground_executor.clone(); assert!( background_executor.is_main_thread(), "must construct App on main thread" @@ -730,32 +731,36 @@ impl App { platform.on_keyboard_layout_change(Box::new({ let app = Rc::downgrade(&app); + let executor = callback_executor.clone(); move || { if let Some(app) = app.upgrade() { - // 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)); - } + executor + .spawn(async move { + let mut cx = 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)(&mut cx)); + }) + .detach(); } } })); platform.on_thermal_state_change(Box::new({ let app = Rc::downgrade(&app); + let executor = callback_executor; move || { if let Some(app) = app.upgrade() { - // 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)); - } + executor + .spawn(async move { + let mut cx = app.borrow_mut(); + cx.thermal_state_observers + .clone() + .retain(&(), move |callback| (callback)(&mut cx)); + }) + .detach(); } } }));