Fix local task dropped on the wrong thread (#28290)

Conrad Irwin created

Release Notes:

- Fixed a panic during shutdown of the remote server

Change summary

crates/gpui/src/platform/linux/dispatcher.rs   | 12 ++++++++++
crates/gpui/src/platform/windows/dispatcher.rs | 22 +++++++++++--------
2 files changed, 24 insertions(+), 10 deletions(-)

Detailed changes

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

@@ -106,7 +106,17 @@ impl PlatformDispatcher for LinuxDispatcher {
     }
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
-        self.main_sender.send(runnable).ok();
+        self.main_sender.send(runnable).unwrap_or_else(|runnable| {
+            // NOTE: Runnable may wrap a Future that is !Send.
+            //
+            // This is usually safe because we only poll it on the main thread.
+            // However if the send fails, we know that:
+            // 1. main_receiver has been dropped (which implies the app is shutting down)
+            // 2. we are on a background thread.
+            // It is not safe to drop something !Send on the wrong thread, and
+            // the app will exit soon anyway, so we must forget the runnable.
+            std::mem::forget(runnable);
+        });
     }
 
     fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

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

@@ -3,7 +3,6 @@ use std::{
     time::Duration,
 };
 
-use anyhow::Context as _;
 use async_task::Runnable;
 use flume::Sender;
 use parking::Parker;
@@ -95,14 +94,8 @@ impl PlatformDispatcher for WindowsDispatcher {
     }
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
-        if self
-            .main_sender
-            .send(runnable)
-            .context("Dispatch on main thread failed")
-            .log_err()
-            .is_some()
-        {
-            unsafe {
+        match self.main_sender.send(runnable) {
+            Ok(_) => unsafe {
                 PostThreadMessageW(
                     self.main_thread_id_win32,
                     WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
@@ -110,6 +103,17 @@ impl PlatformDispatcher for WindowsDispatcher {
                     LPARAM(0),
                 )
                 .log_err();
+            },
+            Err(runnable) => {
+                // NOTE: Runnable may wrap a Future that is !Send.
+                //
+                // This is usually safe because we only poll it on the main thread.
+                // However if the send fails, we know that:
+                // 1. main_receiver has been dropped (which implies the app is shutting down)
+                // 2. we are on a background thread.
+                // It is not safe to drop something !Send on the wrong thread, and
+                // the app will exit soon anyway, so we must forget the runnable.
+                std::mem::forget(runnable);
             }
         }
     }