gpui(windows): Fix apps not quitting if they overwhelm the foreground thread with tasks (#43896)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/gpui/src/platform/windows/platform.rs | 41 +++++++++++----------
1 file changed, 22 insertions(+), 19 deletions(-)

Detailed changes

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

@@ -815,12 +815,14 @@ impl WindowsPlatformInner {
     fn run_foreground_task(&self) -> Option<isize> {
         const MAIN_TASK_TIMEOUT: u128 = 10;
 
-        let mut start = std::time::Instant::now();
-        loop {
-            loop {
+        let start = std::time::Instant::now();
+        'tasks: loop {
+            'timeout_loop: loop {
                 if start.elapsed().as_millis() >= MAIN_TASK_TIMEOUT {
-                    // we spent our budget on gpui tasks, we likely have a lot of work queued so drain system events first
-                    // before returning to main thread task work
+                    log::debug!("foreground task timeout reached");
+                    // we spent our budget on gpui tasks, we likely have a lot of work queued so drain system events first to stay responsive
+                    // then quit out of foreground work to allow us to process other gpui events first before returning back to foreground task work
+                    // if we don't we might not for example process window quit events
                     let mut msg = MSG::default();
                     let process_message = |msg: &_| {
                         if translate_accelerator(msg).is_none() {
@@ -831,39 +833,40 @@ impl WindowsPlatformInner {
                     let peek_msg = |msg: &mut _, msg_kind| unsafe {
                         PeekMessageW(msg, None, 0, 0, PM_REMOVE | msg_kind).as_bool()
                     };
-                    // We process a paint at the start and end only to prevent getting stuck in a paint loop
-                    // due to on going gpui animations
                     if peek_msg(&mut msg, PM_QS_PAINT) {
                         process_message(&msg);
                     }
                     while peek_msg(&mut msg, PM_QS_INPUT) {
                         process_message(&msg);
                     }
-                    if peek_msg(&mut msg, PM_QS_PAINT) {
-                        process_message(&msg);
+                    // Allow the main loop to process other gpui events before going back into `run_foreground_task`
+                    unsafe {
+                        if let Err(_) = PostMessageW(
+                            Some(self.dispatcher.platform_window_handle.as_raw()),
+                            WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
+                            WPARAM(self.validation_number),
+                            LPARAM(0),
+                        ) {
+                            self.dispatcher.wake_posted.store(false, Ordering::Release);
+                        };
                     }
-                    start = std::time::Instant::now();
+                    break 'tasks;
                 }
                 match self.main_receiver.try_recv() {
-                    Err(_) => break,
+                    Err(_) => break 'timeout_loop,
                     Ok(runnable) => WindowsDispatcher::execute_runnable(runnable),
                 }
             }
 
             // Someone could enqueue a Runnable here. The flag is still true, so they will not PostMessage.
             // We need to check for those Runnables after we clear the flag.
-            let dispatcher = self.dispatcher.clone();
-
-            dispatcher.wake_posted.store(false, Ordering::Release);
+            self.dispatcher.wake_posted.store(false, Ordering::Release);
             match self.main_receiver.try_recv() {
+                Err(_) => break 'tasks,
                 Ok(runnable) => {
-                    let _ = dispatcher.wake_posted.swap(true, Ordering::AcqRel);
+                    self.dispatcher.wake_posted.store(true, Ordering::Release);
 
                     WindowsDispatcher::execute_runnable(runnable);
-                    continue;
-                }
-                _ => {
-                    break;
                 }
             }
         }