Fix double borrow error in Window::on_close callbacks when quitting app

Nathan Sobo created

The simplest solution I could come up with was to make quitting the app asynchronous. Calling mac::Platform::quit enqueues a request to quit the app and then allows the call stack to fully return. This ensures we aren't holding a borrow when we quit and invoke all the Window::on_close callbacks. Seems like it should be fine to be async on quitting.

Change summary

gpui/src/platform/mac/platform.rs | 13 +++++++++++++
gpui/src/platform/mac/window.rs   | 15 +++++++++------
2 files changed, 22 insertions(+), 6 deletions(-)

Detailed changes

gpui/src/platform/mac/platform.rs 🔗

@@ -347,7 +347,20 @@ impl platform::Platform for MacPlatform {
     }
 
     fn quit(&self) {
+        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+        // synchronously before this method terminates. If we call `Platform::quit` while holding a
+        // borrow of the app state (which most of the time we will do), we will end up
+        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+        // this, we make quitting the application asynchronous so that we aren't holding borrows to
+        // the app state on the stack when we actually terminate the app.
+
+        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
+
         unsafe {
+            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+        }
+
+        unsafe extern "C" fn quit(_: *mut c_void) {
             let app = NSApplication::sharedApplication(nil);
             let _: () = msg_send![app, terminate: nil];
         }

gpui/src/platform/mac/window.rs 🔗

@@ -380,12 +380,15 @@ extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
 
 extern "C" fn close_window(this: &Object, _: Sel) {
     unsafe {
-        let window_state = get_window_state(this);
-        let close_callback = window_state
-            .as_ref()
-            .try_borrow_mut()
-            .ok()
-            .and_then(|mut window_state| window_state.close_callback.take());
+        let close_callback = {
+            let window_state = get_window_state(this);
+            window_state
+                .as_ref()
+                .try_borrow_mut()
+                .ok()
+                .and_then(|mut window_state| window_state.close_callback.take())
+        };
+
         if let Some(callback) = close_callback {
             callback();
         }