From 101f4c0e6203d7ef04ee5cddb436fed5b6c627ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:07:10 +0000 Subject: [PATCH] Make workaround_windows_shutdown_hang more robust against PID reuse Use a process handle instead of PIDs Fixes #3054 --- src/calibre/gui2/main.py | 21 +++++++++++++++++++-- src/calibre/utils/__init__.py | 14 ++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index cd67e66195..e659aad7e7 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -406,9 +406,26 @@ def workaround_windows_shutdown_hang(timeout: float=1.0): # So it is likely a hang in the Loader Lock. Or memory corruption during exit. # So we run a child process that will wait a second for the parent process # to exit and if it hasnt, will kill it. + # We pass an inheritable process handle so the child does not accidentally + # kill the wrong process due to PID reuse. + import ctypes + from calibre.utils.ipc.simple_worker import start_pipe_worker - start_pipe_worker( - f'from calibre.utils import kill_parent_if_needed; kill_parent_if_needed({os.getpid()!r}, {timeout!r})') + SYNCHRONIZE = 0x00100000 + PROCESS_TERMINATE = 0x0001 + kernel32 = ctypes.windll.kernel32 + # Use OpenProcess on our own PID with bInheritHandle=True to get a real, + # inheritable handle. We set the restype to c_void_p so the 64-bit handle + # value is not truncated on x64 systems. + kernel32.OpenProcess.restype = ctypes.c_void_p + handle = kernel32.OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, True, os.getpid()) + if handle: + try: + start_pipe_worker( + f'from calibre.utils import kill_parent_if_needed; kill_parent_if_needed({handle!r}, {timeout!r})', + pass_fds=(handle,)) + finally: + kernel32.CloseHandle(handle) def run_gui_(opts, args, app, gui_debug=None): diff --git a/src/calibre/utils/__init__.py b/src/calibre/utils/__init__.py index 858b5f3efa..741fd255fa 100644 --- a/src/calibre/utils/__init__.py +++ b/src/calibre/utils/__init__.py @@ -56,23 +56,17 @@ def pickle_binary_string(data): return PROTO + b'\x02' + BINSTRING + struct.pack(b' None: +def kill_parent_if_needed(parent_process_handle: int, timeout: float = 1.0) -> None: import ctypes - import os - import signal - SYNCHRONIZE = 0x00100000 WAIT_OBJECT_0 = 0x00000000 WAIT_TIMEOUT = 0x00000102 kernel32 = ctypes.windll.kernel32 - handle = kernel32.OpenProcess(SYNCHRONIZE, False, parent_pid) - if not handle: - return try: - result = kernel32.WaitForSingleObject(handle, int(timeout * 1000)) + result = kernel32.WaitForSingleObject(parent_process_handle, int(timeout * 1000)) if result == WAIT_OBJECT_0: return if result == WAIT_TIMEOUT: - os.kill(parent_pid, signal.SIGTERM) + kernel32.TerminateProcess(parent_process_handle, 1) finally: - kernel32.CloseHandle(handle) + kernel32.CloseHandle(parent_process_handle)