Skip to content

Commit 38f7da2

Browse files
committed
fix(win32): ensure proper termination of child processes
- Refactor process termination logic to handle both parent and child processes - Increase timeout for graceful termination from 1s to 2s - Consolidate process termination functions to avoid zombie processes - Add proper type hints for process list handling This change ensures that all child processes are properly terminated when killing a parent process on Windows, preventing zombie processes from being left behind.
1 parent a027d75 commit 38f7da2

File tree

2 files changed

+30
-10
lines changed

2 files changed

+30
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies = [
3131
"sse-starlette>=1.6.1",
3232
"pydantic-settings>=2.5.2",
3333
"uvicorn>=0.23.1; sys_platform != 'emscripten'",
34+
"psutil>=5.9.0"
3435
]
3536

3637
[project.optional-dependencies]

src/mcp/client/stdio/win32.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import subprocess
77
import sys
88
from pathlib import Path
9-
from typing import TextIO
10-
9+
from typing import List, TextIO
10+
import psutil
1111
import anyio
1212
from anyio.abc import Process
1313

@@ -87,8 +87,17 @@ async def create_windows_process(
8787
)
8888
return process
8989

90-
9190
async def terminate_windows_process(process: Process):
91+
"""
92+
Terminate a process and subprocesses.
93+
"""
94+
parent = psutil.Process(process.pid)
95+
children = parent.children(recursive=True)
96+
await terminate_psutil_process(children)
97+
await terminate_psutil_process([parent])
98+
99+
100+
async def terminate_psutil_process(processes: List[psutil.Process]):
92101
"""
93102
Terminate a Windows process.
94103
@@ -100,10 +109,20 @@ async def terminate_windows_process(process: Process):
100109
Args:
101110
process: The process to terminate
102111
"""
103-
try:
104-
process.terminate()
105-
with anyio.fail_after(2.0):
106-
await process.wait()
107-
except TimeoutError:
108-
# Force kill if it doesn't terminate
109-
process.kill()
112+
for process in processes:
113+
try:
114+
process.terminate() # Send SIGTERM (or equivalent on Windows)
115+
except psutil.NoSuchProcess:
116+
pass
117+
except Exception:
118+
pass
119+
# Allow some time for children to terminate gracefully
120+
_, alive = psutil.wait_procs(processes, timeout=2.0)
121+
for child in alive:
122+
try:
123+
child.kill() # Force kill if still alive
124+
except psutil.NoSuchProcess:
125+
pass # Already gone
126+
except Exception:
127+
pass
128+

0 commit comments

Comments
 (0)