-
Notifications
You must be signed in to change notification settings - Fork 25
fix: handle Windows shutdown cleanup #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,173 @@ | ||||||||||||||||||
| #[cfg(target_os = "windows")] | ||||||||||||||||||
| mod platform { | ||||||||||||||||||
| use std::{ | ||||||||||||||||||
| mem, | ||||||||||||||||||
| sync::{Mutex, MutexGuard, OnceLock}, | ||||||||||||||||||
| time::Duration, | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| use tauri::{AppHandle, Manager}; | ||||||||||||||||||
| use windows_sys::Win32::{ | ||||||||||||||||||
| Foundation::{GetLastError, SetLastError, HWND, LPARAM, LRESULT, WPARAM}, | ||||||||||||||||||
| System::Threading::SetProcessShutdownParameters, | ||||||||||||||||||
| UI::WindowsAndMessaging::{ | ||||||||||||||||||
| CallWindowProcW, DefWindowProcW, SetWindowLongPtrW, GWLP_WNDPROC, WM_ENDSESSION, | ||||||||||||||||||
| WM_QUERYENDSESSION, WNDPROC, | ||||||||||||||||||
| }, | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| use crate::{append_shutdown_log, BackendState, SYSTEM_SHUTDOWN_STOP_TIMEOUT_MS}; | ||||||||||||||||||
|
|
||||||||||||||||||
| const SHUTDOWN_PRIORITY_EARLY: u32 = 0x100; | ||||||||||||||||||
|
|
||||||||||||||||||
| #[derive(Default)] | ||||||||||||||||||
| struct ShutdownHookState { | ||||||||||||||||||
| app_handle: Option<AppHandle>, | ||||||||||||||||||
| installed: bool, | ||||||||||||||||||
| previous_wndproc: isize, | ||||||||||||||||||
| cleanup_started: bool, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| static SHUTDOWN_HOOK: OnceLock<Mutex<ShutdownHookState>> = OnceLock::new(); | ||||||||||||||||||
|
|
||||||||||||||||||
| fn lock_shutdown_hook<'a>( | ||||||||||||||||||
| hook: &'a Mutex<ShutdownHookState>, | ||||||||||||||||||
| context: &str, | ||||||||||||||||||
| ) -> MutexGuard<'a, ShutdownHookState> { | ||||||||||||||||||
| match hook.lock() { | ||||||||||||||||||
| Ok(guard) => guard, | ||||||||||||||||||
| Err(error) => { | ||||||||||||||||||
| append_shutdown_log(&format!( | ||||||||||||||||||
| "Windows shutdown handler lock poisoned {context}: {error}" | ||||||||||||||||||
| )); | ||||||||||||||||||
| error.into_inner() | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| pub(crate) fn install(app_handle: &AppHandle) { | ||||||||||||||||||
| unsafe { | ||||||||||||||||||
| if SetProcessShutdownParameters(SHUTDOWN_PRIORITY_EARLY, 0) == 0 { | ||||||||||||||||||
| append_shutdown_log("failed to set Windows shutdown priority"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| let Some(window) = app_handle.get_webview_window("main") else { | ||||||||||||||||||
| append_shutdown_log("Windows shutdown handler skipped: main window not found"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| }; | ||||||||||||||||||
| let hwnd = match window.hwnd() { | ||||||||||||||||||
| Ok(hwnd) => hwnd.0, | ||||||||||||||||||
| Err(error) => { | ||||||||||||||||||
| append_shutdown_log(&format!("Windows shutdown handler skipped: {error}")); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| let hook = SHUTDOWN_HOOK.get_or_init(|| Mutex::new(ShutdownHookState::default())); | ||||||||||||||||||
| let mut guard = lock_shutdown_hook(hook, "during install"); | ||||||||||||||||||
|
|
||||||||||||||||||
| guard.app_handle = Some(app_handle.clone()); | ||||||||||||||||||
| if guard.installed { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| let previous = unsafe { | ||||||||||||||||||
| SetLastError(0); | ||||||||||||||||||
| SetWindowLongPtrW(hwnd, GWLP_WNDPROC, shutdown_wndproc as isize) | ||||||||||||||||||
| }; | ||||||||||||||||||
| let last_error = unsafe { GetLastError() }; | ||||||||||||||||||
| if previous == 0 && last_error != 0 { | ||||||||||||||||||
| append_shutdown_log(&format!( | ||||||||||||||||||
| "Windows shutdown handler install failed: error={last_error}" | ||||||||||||||||||
| )); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| guard.installed = true; | ||||||||||||||||||
| guard.previous_wndproc = previous; | ||||||||||||||||||
| append_shutdown_log("Windows shutdown handler installed"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| unsafe extern "system" fn shutdown_wndproc( | ||||||||||||||||||
| hwnd: HWND, | ||||||||||||||||||
| msg: u32, | ||||||||||||||||||
| wparam: WPARAM, | ||||||||||||||||||
| lparam: LPARAM, | ||||||||||||||||||
| ) -> LRESULT { | ||||||||||||||||||
| match msg { | ||||||||||||||||||
| WM_QUERYENDSESSION => { | ||||||||||||||||||
| handle_query_end_session(); | ||||||||||||||||||
| 1 | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+98
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Instead, you should call the previous window procedure and return its result so that the message propagates down the chain.
Suggested change
|
||||||||||||||||||
| WM_ENDSESSION => { | ||||||||||||||||||
| let previous_result = call_previous_wndproc(hwnd, msg, wparam, lparam); | ||||||||||||||||||
| if wparam != 0 { | ||||||||||||||||||
| append_shutdown_log("Windows end session confirmed, exiting desktop process"); | ||||||||||||||||||
| std::process::exit(0); | ||||||||||||||||||
| } | ||||||||||||||||||
| reset_shutdown_cleanup(); | ||||||||||||||||||
| previous_result | ||||||||||||||||||
| } | ||||||||||||||||||
| _ => call_previous_wndproc(hwnd, msg, wparam, lparam), | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn handle_query_end_session() { | ||||||||||||||||||
| let Some(app_handle) = take_shutdown_app_handle_for_cleanup() else { | ||||||||||||||||||
| append_shutdown_log("Windows shutdown cleanup skipped: app handle unavailable"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| append_shutdown_log("Windows shutdown requested, stopping backend quickly"); | ||||||||||||||||||
| let state = app_handle.state::<BackendState>(); | ||||||||||||||||||
| // Keep this bounded wait inside WM_QUERYENDSESSION so taskkill is issued | ||||||||||||||||||
| // before Windows advances to the final session-ending phase. | ||||||||||||||||||
| if let Err(error) = | ||||||||||||||||||
| state.stop_backend_with_timeout(Duration::from_millis(SYSTEM_SHUTDOWN_STOP_TIMEOUT_MS)) | ||||||||||||||||||
| { | ||||||||||||||||||
| append_shutdown_log(&format!("backend stop on Windows shutdown failed: {error}")); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn take_shutdown_app_handle_for_cleanup() -> Option<AppHandle> { | ||||||||||||||||||
| let hook = SHUTDOWN_HOOK.get()?; | ||||||||||||||||||
| let mut guard = lock_shutdown_hook(hook, "during cleanup"); | ||||||||||||||||||
| if guard.cleanup_started { | ||||||||||||||||||
| return None; | ||||||||||||||||||
| } | ||||||||||||||||||
| guard.cleanup_started = true; | ||||||||||||||||||
| guard.app_handle.clone() | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn reset_shutdown_cleanup() { | ||||||||||||||||||
| let Some(hook) = SHUTDOWN_HOOK.get() else { | ||||||||||||||||||
| return; | ||||||||||||||||||
| }; | ||||||||||||||||||
| let mut guard = lock_shutdown_hook(hook, "while resetting cleanup"); | ||||||||||||||||||
| guard.cleanup_started = false; | ||||||||||||||||||
| append_shutdown_log("Windows shutdown canceled, cleanup flag reset"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| unsafe fn call_previous_wndproc( | ||||||||||||||||||
| hwnd: HWND, | ||||||||||||||||||
| msg: u32, | ||||||||||||||||||
| wparam: WPARAM, | ||||||||||||||||||
| lparam: LPARAM, | ||||||||||||||||||
| ) -> LRESULT { | ||||||||||||||||||
| let previous = SHUTDOWN_HOOK | ||||||||||||||||||
| .get() | ||||||||||||||||||
| .map(|hook| lock_shutdown_hook(hook, "while forwarding message").previous_wndproc) | ||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||
|
Comment on lines
+157
to
+160
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||
| if previous == 0 { | ||||||||||||||||||
| return DefWindowProcW(hwnd, msg, wparam, lparam); | ||||||||||||||||||
| } | ||||||||||||||||||
| let previous: WNDPROC = mem::transmute(previous); | ||||||||||||||||||
| CallWindowProcW(previous, hwnd, msg, wparam, lparam) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(target_os = "windows")] | ||||||||||||||||||
| pub(crate) use platform::install; | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(not(target_os = "windows"))] | ||||||||||||||||||
| pub(crate) fn install(_app_handle: &tauri::AppHandle) {} | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locking the
SHUTDOWN_HOOKmutex on every single window message incall_previous_wndprocintroduces significant performance overhead on the main UI thread, especially during high-frequency events like mouse movement or rendering.Since
previous_wndprocis immutable once set, you can store it in a globalAtomicIsizeand load it usingOrdering::Relaxedto completely avoid mutex locking in the hot path.