| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e17b13a commit 7b5ddf4
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,9 @@ | |||
| 2 | 2 | Windows-specific functionality for stdio client operations. | |
| 3 | 3 | """ | |
| 4 | 4 | ||
| 5 | + import os | ||
| 5 | 6 | import shutil | |
| 7 | + import signal | ||
| 6 | 8 | import subprocess | |
| 7 | 9 | import sys | |
| 8 | 10 | from pathlib import Path | |
@@ -76,8 +78,29 @@ async def __aexit__( | |||
| 76 | 78 | exc_tb: object | None, | |
| 77 | 79 | ) -> None: | |
| 78 | 80 | """Terminate and wait on process exit inside a thread.""" | |
| 79 | - self.popen.terminate() | ||
| 80 | - await to_thread.run_sync(self.popen.wait) | ||
| 81 | + # Try graceful shutdown with CTRL_C_EVENT on Windows | ||
| 82 | + if sys.platform == "win32" and hasattr(signal, "CTRL_C_EVENT"): | ||
| 83 | + try: | ||
| 84 | + # Send CTRL_C_EVENT for graceful shutdown | ||
| 85 | + os.kill(self.popen.pid, getattr(signal, "CTRL_C_EVENT")) | ||
| 86 | + # Wait for process to exit gracefully (2 second timeout) | ||
| 87 | + await to_thread.run_sync(lambda: self.popen.wait(timeout=2)) | ||
| 88 | + except (subprocess.TimeoutExpired, ProcessLookupError, OSError): | ||
| 89 | + # If graceful shutdown fails, fall back to terminate | ||
| 90 | + try: | ||
| 91 | + self.popen.terminate() | ||
| 92 | + await to_thread.run_sync(self.popen.wait) | ||
| 93 | + except ProcessLookupError: | ||
| 94 | + # Process already exited | ||
| 95 | + pass | ||
| 96 | + else: | ||
| 97 | + # Non-Windows or fallback behavior | ||
| 98 | + try: | ||
| 99 | + self.popen.terminate() | ||
| 100 | + await to_thread.run_sync(self.popen.wait) | ||
| 101 | + except ProcessLookupError: | ||
| 102 | + # Process already exited | ||
| 103 | + pass | ||
| 81 | 104 | ||
| 82 | 105 | # Close the file handles to prevent ResourceWarning | |
| 83 | 106 | if self.stdin: | |
@@ -163,20 +186,36 @@ async def create_windows_process( | |||
| 163 | 186 | ||
| 164 | 187 | async def terminate_windows_process(process: Process | FallbackProcess): | |
| 165 | 188 | """ | |
| 166 | - Terminate a Windows process. | ||
| 189 | + Terminate a Windows process gracefully. | ||
| 167 | 190 | ||
| 168 | - Note: On Windows, terminating a process with process.terminate() doesn't | ||
| 169 | - always guarantee immediate process termination. | ||
| 170 | - So we give it 2s to exit, or we call process.kill() | ||
| 171 | - which sends a SIGKILL equivalent signal. | ||
| 191 | + First attempts graceful shutdown using CTRL_C_EVENT signal, | ||
| 192 | + which allows the process to run cleanup code. | ||
| 193 | + Falls back to terminate() and then kill() if graceful shutdown fails. | ||
| 172 | 194 | ||
| 173 | 195 | Args: | |
| 174 | 196 | process: The process to terminate | |
| 175 | 197 | """ | |
| 198 | + # Try graceful shutdown with CTRL_C_EVENT first | ||
| 199 | + if isinstance(process, FallbackProcess) and hasattr(signal, "CTRL_C_EVENT"): | ||
| 200 | + try: | ||
| 201 | + # Send CTRL_C_EVENT for graceful shutdown | ||
| 202 | + os.kill(process.popen.pid, getattr(signal, "CTRL_C_EVENT")) | ||
| 203 | + with anyio.fail_after(2.0): | ||
| 204 | + await process.wait() | ||
| 205 | + return | ||
| 206 | + except (TimeoutError, ProcessLookupError, OSError): | ||
| 207 | + # If CTRL_C_EVENT failed or timed out, continue to forceful termination | ||
| 208 | + pass | ||
| 209 | + | ||
| 210 | + # Fall back to terminate | ||
| 176 | 211 | try: | |
| 177 | 212 | process.terminate() | |
| 178 | 213 | with anyio.fail_after(2.0): | |
| 179 | 214 | await process.wait() | |
| 180 | 215 | except TimeoutError: | |
| 181 | 216 | # Force kill if it doesn't terminate | |
| 182 | - process.kill() | ||
| 217 | + try: | ||
| 218 | + process.kill() | ||
| 219 | + except ProcessLookupError: | ||
| 220 | + # Process already exited | ||
| 221 | + pass | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments