gpui: Make all foreground tasks drop when the app dies (#46219)

Mikayla Maki created

This expands the liveness check to cover all foreground tasks that don't
take the app as a context (as some of their internal futures might.)

Release Notes:

- N/A

Change summary

crates/gpui/src/app.rs                            |  20 +
crates/gpui/src/app/async_context.rs              |   8 
crates/gpui/src/app/test_context.rs               |   7 
crates/gpui/src/app/visual_test_context.rs        |   6 
crates/gpui/src/executor.rs                       | 154 ++++------------
crates/gpui/src/platform.rs                       |  22 +-
crates/gpui/src/platform/linux/headless/client.rs |   4 
crates/gpui/src/platform/linux/platform.rs        |   7 
crates/gpui/src/platform/linux/wayland/client.rs  |   4 
crates/gpui/src/platform/linux/x11/client.rs      |   4 
crates/gpui/src/platform/mac/platform.rs          |  10 
crates/gpui/src/platform/test/dispatcher.rs       |   9 
crates/gpui/src/platform/windows/platform.rs      |   4 
13 files changed, 96 insertions(+), 163 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -138,8 +138,10 @@ impl Application {
         #[cfg(any(test, feature = "test-support"))]
         log::info!("GPUI was compiled in test mode");
 
+        let liveness = Arc::new(());
         Self(App::new_app(
-            current_platform(false),
+            current_platform(false, Arc::downgrade(&liveness)),
+            liveness,
             Arc::new(()),
             Arc::new(NullHttpClient),
         ))
@@ -149,8 +151,10 @@ impl Application {
     /// but makes it possible to run an application in an context like
     /// SSH, where GUI applications are not allowed.
     pub fn headless() -> Self {
+        let liveness = Arc::new(());
         Self(App::new_app(
-            current_platform(true),
+            current_platform(true, Arc::downgrade(&liveness)),
+            liveness,
             Arc::new(()),
             Arc::new(NullHttpClient),
         ))
@@ -584,7 +588,7 @@ impl GpuiMode {
 /// You need a reference to an `App` to access the state of a [Entity].
 pub struct App {
     pub(crate) this: Weak<AppCell>,
-    pub(crate) liveness: std::sync::Arc<()>,
+    pub(crate) _liveness: Arc<()>,
     pub(crate) platform: Rc<dyn Platform>,
     pub(crate) mode: GpuiMode,
     text_system: Arc<TextSystem>,
@@ -646,13 +650,14 @@ impl App {
     #[allow(clippy::new_ret_no_self)]
     pub(crate) fn new_app(
         platform: Rc<dyn Platform>,
+        liveness: Arc<()>,
         asset_source: Arc<dyn AssetSource>,
         http_client: Arc<dyn HttpClient>,
     ) -> Rc<AppCell> {
-        let executor = platform.background_executor();
+        let background_executor = platform.background_executor();
         let foreground_executor = platform.foreground_executor();
         assert!(
-            executor.is_main_thread(),
+            background_executor.is_main_thread(),
             "must construct App on main thread"
         );
 
@@ -664,7 +669,7 @@ impl App {
         let app = Rc::new_cyclic(|this| AppCell {
             app: RefCell::new(App {
                 this: this.clone(),
-                liveness: std::sync::Arc::new(()),
+                _liveness: liveness,
                 platform: platform.clone(),
                 text_system,
                 text_rendering_mode: Rc::new(Cell::new(TextRenderingMode::default())),
@@ -673,7 +678,7 @@ impl App {
                 flushing_effects: false,
                 pending_updates: 0,
                 active_drag: None,
-                background_executor: executor,
+                background_executor,
                 foreground_executor,
                 svg_renderer: SvgRenderer::new(asset_source.clone()),
                 loading_assets: Default::default(),
@@ -1494,7 +1499,6 @@ impl App {
     pub fn to_async(&self) -> AsyncApp {
         AsyncApp {
             app: self.this.clone(),
-            liveness_token: std::sync::Arc::downgrade(&self.liveness),
             background_executor: self.background_executor.clone(),
             foreground_executor: self.foreground_executor.clone(),
         }

crates/gpui/src/app/async_context.rs 🔗

@@ -16,7 +16,6 @@ use super::{Context, WeakEntity};
 #[derive(Clone)]
 pub struct AsyncApp {
     pub(crate) app: Weak<AppCell>,
-    pub(crate) liveness_token: std::sync::Weak<()>,
     pub(crate) background_executor: BackgroundExecutor,
     pub(crate) foreground_executor: ForegroundExecutor,
 }
@@ -186,7 +185,7 @@ impl AsyncApp {
     {
         let mut cx = self.clone();
         self.foreground_executor
-            .spawn_context(self.liveness_token.clone(), async move { f(&mut cx).await })
+            .spawn(async move { f(&mut cx).await })
     }
 
     /// Determine whether global state of the specified type has been assigned.
@@ -335,10 +334,7 @@ impl AsyncWindowContext {
     {
         let mut cx = self.clone();
         self.foreground_executor
-            .spawn_context(
-                self.app.liveness_token.clone(),
-                async move { f(&mut cx).await },
-            )
+            .spawn(async move { f(&mut cx).await })
     }
 
     /// Present a platform dialog.

crates/gpui/src/app/test_context.rs 🔗

@@ -125,14 +125,16 @@ impl TestAppContext {
     /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
     pub fn build(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
         let arc_dispatcher = Arc::new(dispatcher.clone());
+        let liveness = std::sync::Arc::new(());
         let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
+        let foreground_executor =
+            ForegroundExecutor::new(arc_dispatcher, Arc::downgrade(&liveness));
         let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
         let asset_source = Arc::new(());
         let http_client = http_client::FakeHttpClient::with_404_response();
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
 
-        let mut app = App::new_app(platform.clone(), asset_source, http_client);
+        let app = App::new_app(platform.clone(), liveness, asset_source, http_client);
         app.borrow_mut().mode = GpuiMode::test();
 
         Self {
@@ -405,7 +407,6 @@ impl TestAppContext {
     pub fn to_async(&self) -> AsyncApp {
         AsyncApp {
             app: Rc::downgrade(&self.app),
-            liveness_token: std::sync::Arc::downgrade(&self.app.borrow().liveness),
             background_executor: self.background_executor.clone(),
             foreground_executor: self.foreground_executor.clone(),
         }

crates/gpui/src/app/visual_test_context.rs 🔗

@@ -37,7 +37,9 @@ impl VisualTestAppContext {
     /// - Screenshots can be captured via ScreenCaptureKit
     /// - All platform APIs work as they do in production
     pub fn new() -> Self {
-        let platform = current_platform(false);
+        let liveness = Arc::new(());
+        let liveness_weak = Arc::downgrade(&liveness);
+        let platform = current_platform(false, liveness_weak);
         let background_executor = platform.background_executor();
         let foreground_executor = platform.foreground_executor();
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
@@ -45,7 +47,7 @@ impl VisualTestAppContext {
         let asset_source = Arc::new(());
         let http_client = http_client::FakeHttpClient::with_404_response();
 
-        let mut app = App::new_app(platform.clone(), asset_source, http_client);
+        let mut app = App::new_app(platform.clone(), liveness, asset_source, http_client);
         app.borrow_mut().mode = GpuiMode::test();
 
         Self {

crates/gpui/src/executor.rs 🔗

@@ -19,7 +19,7 @@ use std::{
     thread::{self, ThreadId},
     time::{Duration, Instant},
 };
-use util::TryFutureExt;
+use util::TryFutureExt as _;
 use waker_fn::waker_fn;
 
 #[cfg(any(test, feature = "test-support"))]
@@ -44,6 +44,7 @@ pub struct BackgroundExecutor {
 pub struct ForegroundExecutor {
     #[doc(hidden)]
     pub dispatcher: Arc<dyn PlatformDispatcher>,
+    liveness: std::sync::Weak<()>,
     not_send: PhantomData<Rc<()>>,
 }
 
@@ -776,9 +777,10 @@ impl BackgroundExecutor {
 /// ForegroundExecutor runs things on the main thread.
 impl ForegroundExecutor {
     /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
-    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>, liveness: std::sync::Weak<()>) -> Self {
         Self {
             dispatcher,
+            liveness,
             not_send: PhantomData,
         }
     }
@@ -789,7 +791,7 @@ impl ForegroundExecutor {
     where
         R: 'static,
     {
-        self.inner_spawn(None, Priority::default(), future)
+        self.inner_spawn(self.liveness.clone(), Priority::default(), future)
     }
 
     /// Enqueues the given Task to run on the main thread at some point in the future.
@@ -802,25 +804,13 @@ impl ForegroundExecutor {
     where
         R: 'static,
     {
-        self.inner_spawn(None, priority, future)
-    }
-
-    #[track_caller]
-    pub(crate) fn spawn_context<R>(
-        &self,
-        app: std::sync::Weak<()>,
-        future: impl Future<Output = R> + 'static,
-    ) -> Task<R>
-    where
-        R: 'static,
-    {
-        self.inner_spawn(Some(app), Priority::default(), future)
+        self.inner_spawn(self.liveness.clone(), priority, future)
     }
 
     #[track_caller]
     pub(crate) fn inner_spawn<R>(
         &self,
-        app: Option<std::sync::Weak<()>>,
+        app: std::sync::Weak<()>,
         priority: Priority,
         future: impl Future<Output = R> + 'static,
     ) -> Task<R>
@@ -835,7 +825,7 @@ impl ForegroundExecutor {
             dispatcher: Arc<dyn PlatformDispatcher>,
             future: AnyLocalFuture<R>,
             location: &'static core::panic::Location<'static>,
-            app: Option<std::sync::Weak<()>>,
+            app: std::sync::Weak<()>,
             priority: Priority,
         ) -> Task<R> {
             let (runnable, task) = spawn_local_with_source_location(
@@ -843,7 +833,10 @@ impl ForegroundExecutor {
                 move |runnable| {
                     dispatcher.dispatch_on_main_thread(RunnableVariant::Meta(runnable), priority)
                 },
-                RunnableMeta { location, app },
+                RunnableMeta {
+                    location,
+                    app: Some(app),
+                },
             );
             runnable.schedule();
             Task(TaskState::Spawned(task))
@@ -989,24 +982,34 @@ mod test {
     use rand::SeedableRng;
     use std::cell::RefCell;
 
-    #[test]
-    fn sanity_test_tasks_run() {
+    /// Helper to create test infrastructure.
+    /// Returns (dispatcher, background_executor, app) where app's foreground_executor has liveness.
+    fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
         let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
         let arc_dispatcher = Arc::new(dispatcher.clone());
+        // Create liveness for task cancellation
+        let liveness = std::sync::Arc::new(());
+        let liveness_weak = std::sync::Arc::downgrade(&liveness);
         let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
+        let foreground_executor = ForegroundExecutor::new(arc_dispatcher, liveness_weak);
 
-        let platform = TestPlatform::new(background_executor, foreground_executor.clone());
+        let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
         let asset_source = Arc::new(());
         let http_client = http_client::FakeHttpClient::with_404_response();
 
-        let app = App::new_app(platform, asset_source, http_client);
-        let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+        let app = App::new_app(platform, liveness, asset_source, http_client);
+        (dispatcher, background_executor, app)
+    }
+
+    #[test]
+    fn sanity_test_tasks_run() {
+        let (dispatcher, _background_executor, app) = create_test_app();
+        let foreground_executor = app.borrow().foreground_executor.clone();
 
         let task_ran = Rc::new(RefCell::new(false));
 
         foreground_executor
-            .spawn_context(liveness_token, {
+            .spawn({
                 let task_ran = Rc::clone(&task_ran);
                 async move {
                     *task_ran.borrow_mut() = true;
@@ -1026,24 +1029,15 @@ mod test {
 
     #[test]
     fn test_task_cancelled_when_app_dropped() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let arc_dispatcher = Arc::new(dispatcher.clone());
-        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
-        let platform = TestPlatform::new(background_executor, foreground_executor.clone());
-        let asset_source = Arc::new(());
-        let http_client = http_client::FakeHttpClient::with_404_response();
-
-        let app = App::new_app(platform, asset_source, http_client);
-        let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+        let (dispatcher, _background_executor, app) = create_test_app();
+        let foreground_executor = app.borrow().foreground_executor.clone();
         let app_weak = Rc::downgrade(&app);
 
         let task_ran = Rc::new(RefCell::new(false));
         let task_ran_clone = Rc::clone(&task_ran);
 
         foreground_executor
-            .spawn_context(liveness_token, async move {
+            .spawn(async move {
                 *task_ran_clone.borrow_mut() = true;
             })
             .detach();
@@ -1063,17 +1057,8 @@ mod test {
 
     #[test]
     fn test_nested_tasks_both_cancel() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let arc_dispatcher = Arc::new(dispatcher.clone());
-        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
-        let platform = TestPlatform::new(background_executor, foreground_executor.clone());
-        let asset_source = Arc::new(());
-        let http_client = http_client::FakeHttpClient::with_404_response();
-
-        let app = App::new_app(platform, asset_source, http_client);
-        let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+        let (dispatcher, _background_executor, app) = create_test_app();
+        let foreground_executor = app.borrow().foreground_executor.clone();
         let app_weak = Rc::downgrade(&app);
 
         let outer_completed = Rc::new(RefCell::new(false));
@@ -1087,13 +1072,11 @@ mod test {
         // Channel to block the inner task until we're ready
         let (tx, rx) = futures::channel::oneshot::channel::<()>();
 
-        // We need clones of executor and liveness_token for the inner spawn
         let inner_executor = foreground_executor.clone();
-        let inner_liveness_token = liveness_token.clone();
 
         foreground_executor
-            .spawn_context(liveness_token, async move {
-                let inner_task = inner_executor.spawn_context(inner_liveness_token, {
+            .spawn(async move {
+                let inner_task = inner_executor.spawn({
                     let inner_flag = Rc::clone(&inner_flag);
                     async move {
                         rx.await.ok();
@@ -1148,56 +1131,14 @@ mod test {
         );
     }
 
-    #[test]
-    fn test_task_without_app_tracking_still_runs() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let arc_dispatcher = Arc::new(dispatcher.clone());
-        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
-        let platform = TestPlatform::new(background_executor, foreground_executor.clone());
-        let asset_source = Arc::new(());
-        let http_client = http_client::FakeHttpClient::with_404_response();
-
-        let app = App::new_app(platform, asset_source, http_client);
-        let app_weak = Rc::downgrade(&app);
-
-        let task_ran = Rc::new(RefCell::new(false));
-        let task_ran_clone = Rc::clone(&task_ran);
-
-        let _task = foreground_executor.spawn(async move {
-            *task_ran_clone.borrow_mut() = true;
-        });
-
-        drop(app);
-
-        assert!(app_weak.upgrade().is_none(), "App should have been dropped");
-
-        dispatcher.run_until_parked();
-
-        assert!(
-            *task_ran.borrow(),
-            "Task without app tracking should still run after app is dropped"
-        );
-    }
-
     #[test]
     #[should_panic]
     fn test_polling_cancelled_task_panics() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let arc_dispatcher = Arc::new(dispatcher.clone());
-        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
-        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
-        let asset_source = Arc::new(());
-        let http_client = http_client::FakeHttpClient::with_404_response();
-
-        let app = App::new_app(platform, asset_source, http_client);
-        let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+        let (dispatcher, background_executor, app) = create_test_app();
+        let foreground_executor = app.borrow().foreground_executor.clone();
         let app_weak = Rc::downgrade(&app);
 
-        let task = foreground_executor.spawn_context(liveness_token, async move { 42 });
+        let task = foreground_executor.spawn(async move { 42 });
 
         drop(app);
 
@@ -1210,22 +1151,11 @@ mod test {
 
     #[test]
     fn test_polling_cancelled_task_returns_none_with_fallible() {
-        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
-        let arc_dispatcher = Arc::new(dispatcher.clone());
-        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
-        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
-        let asset_source = Arc::new(());
-        let http_client = http_client::FakeHttpClient::with_404_response();
-
-        let app = App::new_app(platform, asset_source, http_client);
-        let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+        let (dispatcher, background_executor, app) = create_test_app();
+        let foreground_executor = app.borrow().foreground_executor.clone();
         let app_weak = Rc::downgrade(&app);
 
-        let task = foreground_executor
-            .spawn_context(liveness_token, async move { 42 })
-            .fallible();
+        let task = foreground_executor.spawn(async move { 42 }).fallible();
 
         drop(app);
 

crates/gpui/src/platform.rs 🔗

@@ -92,43 +92,45 @@ pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}
 
 /// Returns a background executor for the current platform.
 pub fn background_executor() -> BackgroundExecutor {
-    current_platform(true).background_executor()
+    // For standalone background executor, use a dead liveness since there's no App.
+    // Weak::new() creates a weak reference that always returns None on upgrade.
+    current_platform(true, std::sync::Weak::new()).background_executor()
 }
 
 #[cfg(target_os = "macos")]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
-    Rc::new(MacPlatform::new(headless))
+pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
+    Rc::new(MacPlatform::new(headless, liveness))
 }
 
 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
+pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
     #[cfg(feature = "x11")]
     use anyhow::Context as _;
 
     if headless {
-        return Rc::new(HeadlessClient::new());
+        return Rc::new(HeadlessClient::new(liveness));
     }
 
     match guess_compositor() {
         #[cfg(feature = "wayland")]
-        "Wayland" => Rc::new(WaylandClient::new()),
+        "Wayland" => Rc::new(WaylandClient::new(liveness)),
 
         #[cfg(feature = "x11")]
         "X11" => Rc::new(
-            X11Client::new()
+            X11Client::new(liveness)
                 .context("Failed to initialize X11 client.")
                 .unwrap(),
         ),
 
-        "Headless" => Rc::new(HeadlessClient::new()),
+        "Headless" => Rc::new(HeadlessClient::new(liveness)),
         _ => unreachable!(),
     }
 }
 
 #[cfg(target_os = "windows")]
-pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
+pub(crate) fn current_platform(_headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
     Rc::new(
-        WindowsPlatform::new()
+        WindowsPlatform::new(liveness)
             .inspect_err(|err| show_error("Failed to launch", err.to_string()))
             .unwrap(),
     )

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -21,10 +21,10 @@ pub struct HeadlessClientState {
 pub(crate) struct HeadlessClient(Rc<RefCell<HeadlessClientState>>);
 
 impl HeadlessClient {
-    pub(crate) fn new() -> Self {
+    pub(crate) fn new(liveness: std::sync::Weak<()>) -> Self {
         let event_loop = EventLoop::try_new().unwrap();
 
-        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
 
         let handle = event_loop.handle();
 

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -149,7 +149,10 @@ pub(crate) struct LinuxCommon {
 }
 
 impl LinuxCommon {
-    pub fn new(signal: LoopSignal) -> (Self, PriorityQueueCalloopReceiver<RunnableVariant>) {
+    pub fn new(
+        signal: LoopSignal,
+        liveness: std::sync::Weak<()>,
+    ) -> (Self, PriorityQueueCalloopReceiver<RunnableVariant>) {
         let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
@@ -165,7 +168,7 @@ impl LinuxCommon {
 
         let common = LinuxCommon {
             background_executor,
-            foreground_executor: ForegroundExecutor::new(dispatcher),
+            foreground_executor: ForegroundExecutor::new(dispatcher, liveness),
             text_system,
             appearance: WindowAppearance::Light,
             auto_hide_scrollbars: false,

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -453,7 +453,7 @@ fn wl_output_version(version: u32) -> u32 {
 }
 
 impl WaylandClient {
-    pub(crate) fn new() -> Self {
+    pub(crate) fn new(liveness: std::sync::Weak<()>) -> Self {
         let conn = Connection::connect_to_env().unwrap();
 
         let (globals, mut event_queue) =
@@ -490,7 +490,7 @@ impl WaylandClient {
 
         let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
 
-        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
 
         let handle = event_loop.handle();
         handle

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -297,10 +297,10 @@ impl X11ClientStatePtr {
 pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
 
 impl X11Client {
-    pub(crate) fn new() -> anyhow::Result<Self> {
+    pub(crate) fn new(liveness: std::sync::Weak<()>) -> anyhow::Result<Self> {
         let event_loop = EventLoop::try_new()?;
 
-        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
 
         let handle = event_loop.handle();
 

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

@@ -173,14 +173,8 @@ pub(crate) struct MacPlatformState {
     keyboard_mapper: Rc<MacKeyboardMapper>,
 }
 
-impl Default for MacPlatform {
-    fn default() -> Self {
-        Self::new(false)
-    }
-}
-
 impl MacPlatform {
-    pub(crate) fn new(headless: bool) -> Self {
+    pub(crate) fn new(headless: bool, liveness: std::sync::Weak<()>) -> Self {
         let dispatcher = Arc::new(MacDispatcher);
 
         #[cfg(feature = "font-kit")]
@@ -196,7 +190,7 @@ impl MacPlatform {
             headless,
             text_system,
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
-            foreground_executor: ForegroundExecutor::new(dispatcher),
+            foreground_executor: ForegroundExecutor::new(dispatcher, liveness),
             renderer_context: renderer::Context::default(),
             general_pasteboard: Pasteboard::general(),
             find_pasteboard: Pasteboard::find(),

crates/gpui/src/platform/test/dispatcher.rs 🔗

@@ -180,12 +180,13 @@ impl TestDispatcher {
             RunnableVariant::Meta(runnable) => {
                 if !runnable.metadata().is_app_alive() {
                     drop(runnable);
-                    self.state.lock().is_main_thread = was_main_thread;
-                    return true;
+                } else {
+                    runnable.run();
                 }
-                runnable.run()
             }
-            RunnableVariant::Compat(runnable) => runnable.run(),
+            RunnableVariant::Compat(runnable) => {
+                runnable.run();
+            }
         };
 
         self.state.lock().is_main_thread = was_main_thread;

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -93,7 +93,7 @@ impl WindowsPlatformState {
 }
 
 impl WindowsPlatform {
-    pub(crate) fn new() -> Result<Self> {
+    pub(crate) fn new(liveness: std::sync::Weak<()>) -> Result<Self> {
         unsafe {
             OleInitialize(None).context("unable to initialize Windows OLE")?;
         }
@@ -148,7 +148,7 @@ impl WindowsPlatform {
         let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
             .is_ok_and(|value| value == "true" || value == "1");
         let background_executor = BackgroundExecutor::new(dispatcher.clone());
-        let foreground_executor = ForegroundExecutor::new(dispatcher);
+        let foreground_executor = ForegroundExecutor::new(dispatcher, liveness);
 
         let drop_target_helper: IDropTargetHelper = unsafe {
             CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)