mirror of
https://github.com/warpdotdev/warp.git
synced 2026-05-06 23:32:51 +08:00
fix(windows): prevent warp.exe from hanging after window closes (#10202)
When the user closes Warp on Windows, the window hides immediately but `std::process::exit(0)` is only called after `on_will_terminate` returns. `shutdown_all_pty_event_loops` (Windows-only) joins PTY event loop threads with no timeout; if a thread fails to exit the process hangs indefinitely. Add a 5-second timeout to `shutdown_event_loop` on Windows so that a stuck PTY event loop thread never prevents the process from exiting. If the timeout fires, the thread is left to be killed by `std::process::exit(0)`, and the OS will close the ConPTY handle which signals OpenConsole to exit on its own. Also surface mio `Waker::wake()` failures as logged errors instead of silently swallowing them, making it easier to diagnose if wake-up failures are the root cause of a stuck event loop on a given machine. Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
@@ -91,14 +91,14 @@ impl<T> Sender<T> {
|
||||
/// This works the same way as [`mpsc::Sender::send`]. After sending the
|
||||
/// value, it wakes upthe [`mio::poll::Poll`].
|
||||
///
|
||||
/// Note that I/O errors from waking up the [`mio::poll::Poll`] are
|
||||
/// swallowed.
|
||||
pub fn send(&self, t: T) -> Result<(), SendError<T>> {
|
||||
self.tx.send(t)?;
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
if let Some(waker) = &mut state.waker {
|
||||
let _ = waker.wake();
|
||||
if let Err(e) = waker.wake() {
|
||||
log::error!("PTY mio Waker::wake() failed: {e:?}; event loop may not wake up to process shutdown");
|
||||
}
|
||||
} else {
|
||||
state.needs_wake_on_register = true;
|
||||
}
|
||||
|
||||
@@ -195,6 +195,38 @@ impl TerminalManager {
|
||||
}
|
||||
|
||||
if let Some(join_handle) = self.event_loop_handle.take() {
|
||||
// On Windows, poll with a timeout instead of blocking indefinitely.
|
||||
// If the event loop hangs (e.g. due to a pipe read or mio waker failure),
|
||||
// a stuck join would prevent on_will_terminate from returning and thus
|
||||
// prevent std::process::exit(0) from ever being called — leaving the
|
||||
// warp.exe process alive with no visible window (APP-3702).
|
||||
//
|
||||
// If we time out, std::process::exit(0) will still kill the thread.
|
||||
// The ConPTY handle will be closed by the OS when the process exits,
|
||||
// which signals OpenConsole to exit on its own.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
const SHUTDOWN_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_secs(5);
|
||||
const POLL_INTERVAL: std::time::Duration =
|
||||
std::time::Duration::from_millis(10);
|
||||
let deadline = std::time::Instant::now() + SHUTDOWN_TIMEOUT;
|
||||
while !join_handle.is_finished() {
|
||||
if std::time::Instant::now() >= deadline {
|
||||
log::warn!(
|
||||
"PTY event loop did not exit within {SHUTDOWN_TIMEOUT:?}; \
|
||||
proceeding with app shutdown"
|
||||
);
|
||||
self.inactive_pty_reads_rx.close();
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(POLL_INTERVAL);
|
||||
}
|
||||
if let Err(e) = join_handle.join() {
|
||||
log::error!("Failed to join event loop handle {e:?}");
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
if let Err(e) = join_handle.join() {
|
||||
log::error!("Failed to join event loop handle {e:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user