Make workaround_windows_shutdown_hang more robust against PID reuse

Use a process handle instead of PIDs

Fixes #3054
This commit is contained in:
copilot-swe-agent[bot]
2026-03-20 09:07:10 +00:00
committed by Kovid Goyal
parent 500958021e
commit 101f4c0e62
2 changed files with 23 additions and 12 deletions
+19 -2
View File
@@ -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):
+4 -10
View File
@@ -56,23 +56,17 @@ def pickle_binary_string(data):
return PROTO + b'\x02' + BINSTRING + struct.pack(b'<i', len(data)) + data + STOP
def kill_parent_if_needed(parent_pid: int, timeout: float = 1.0) -> 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)