diff --git a/.changes/single-instance-windows-startup-race.md b/.changes/single-instance-windows-startup-race.md new file mode 100644 index 000000000..798c5744e --- /dev/null +++ b/.changes/single-instance-windows-startup-race.md @@ -0,0 +1,5 @@ +--- +"single-instance": patch +--- + +Fix race condition on Windows where a secondary instance fails to exit if `FindWindowW` misses the target event window during startup. diff --git a/plugins/single-instance/src/platform_impl/windows.rs b/plugins/single-instance/src/platform_impl/windows.rs index 746b2ffad..27a3581f3 100644 --- a/plugins/single-instance/src/platform_impl/windows.rs +++ b/plugins/single-instance/src/platform_impl/windows.rs @@ -79,7 +79,17 @@ pub fn init(callback: Box>) -> TauriPlugin if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS { unsafe { - let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr()); + // When instances launch concurrently (e.g. quick double-click), the first + // instance publishes the mutex before its event target window is created. + // Poll with a bounded retry before giving up on message delivery. + let mut hwnd = std::ptr::null_mut(); + for _ in 0..40 { + hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr()); + if !hwnd.is_null() { + break; + } + std::thread::sleep(std::time::Duration::from_millis(25)); + } if !hwnd.is_null() { // Windows lets us bring a window to the front, but not the first @@ -125,10 +135,14 @@ pub fn init(callback: Box>) -> TauriPlugin GetLastError() ); } - - app.cleanup_before_exit(); - std::process::exit(0); + } else { + tracing::warn!( + "single_instance detected an existing instance mutex, but the target window was not found within 1000ms" + ); } + + app.cleanup_before_exit(); + std::process::exit(0); } } else { app.manage(MutexHandle(hmutex as _));