Skip to content

Commit 5771abc

Browse files
Address @ihrpr review feedback
- Add SIGTERM_IGNORING_PROCESS_TIMEOUT constant in tests to document timeout behavior - Add PROCESS_TERMINATION_TIMEOUT constant to replace magic number in stdio client - Restore deprecated terminate_windows_process function with original functionality to maintain backward compatibility for external users The deprecated function is marked using @deprecated decorator following the codebase convention, while preserving its original terminate-wait-kill behavior.
1 parent c9b43bc commit 5771abc

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

src/mcp/client/stdio/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
3838
)
3939

40+
# Timeout for process termination before falling back to force kill
41+
PROCESS_TERMINATION_TIMEOUT = 2.0
42+
4043

4144
def get_default_environment() -> dict[str, str]:
4245
"""
@@ -180,7 +183,7 @@ async def stdin_writer():
180183
# Clean up process to prevent any dangling orphaned processes
181184
try:
182185
process.terminate()
183-
with anyio.fail_after(2.0):
186+
with anyio.fail_after(PROCESS_TERMINATION_TIMEOUT):
184187
await process.wait()
185188
except TimeoutError:
186189
# If process doesn't terminate in time, force kill it

src/mcp/client/stdio/win32.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from anyio import to_thread
1313
from anyio.abc import Process
1414
from anyio.streams.file import FileReadStream, FileWriteStream
15+
from typing_extensions import deprecated
1516

1617

1718
def get_windows_executable_command(command: str) -> str:
@@ -199,3 +200,28 @@ async def _create_windows_fallback_process(
199200
bufsize=0,
200201
)
201202
return FallbackProcess(popen_obj)
203+
204+
205+
@deprecated(
206+
"terminate_windows_process is deprecated and will be removed in a future version. "
207+
"Process termination is now handled internally by the stdio_client context manager."
208+
)
209+
async def terminate_windows_process(process: Process | FallbackProcess):
210+
"""
211+
Terminate a Windows process.
212+
213+
Note: On Windows, terminating a process with process.terminate() doesn't
214+
always guarantee immediate process termination.
215+
So we give it 2s to exit, or we call process.kill()
216+
which sends a SIGKILL equivalent signal.
217+
218+
Args:
219+
process: The process to terminate
220+
"""
221+
try:
222+
process.terminate()
223+
with anyio.fail_after(2.0):
224+
await process.wait()
225+
except TimeoutError:
226+
# Force kill if it doesn't terminate
227+
process.kill()

tests/client/test_stdio.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
from mcp.shared.message import SessionMessage
1616
from mcp.types import CONNECTION_CLOSED, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
1717

18+
# Timeout for cleanup of processes that ignore SIGTERM
19+
# This timeout ensures the test fails quickly if the cleanup logic doesn't have
20+
# proper fallback mechanisms (SIGINT/SIGKILL) for processes that ignore SIGTERM
21+
SIGTERM_IGNORING_PROCESS_TIMEOUT = 5.0
22+
1823
tee: str = shutil.which("tee") # type: ignore
1924
python: str = shutil.which("python") # type: ignore
2025

@@ -200,14 +205,16 @@ def sigint_handler(signum, frame):
200205

201206
# Should complete quickly even with SIGTERM-ignoring process
202207
# This will fail if cleanup only uses process.terminate() without fallback
203-
assert elapsed < 5.0, (
208+
assert elapsed < SIGTERM_IGNORING_PROCESS_TIMEOUT, (
204209
f"stdio_client cleanup took {elapsed:.1f} seconds with SIGTERM-ignoring process. "
205-
f"Expected < 5.0 seconds. This suggests the cleanup needs SIGINT/SIGKILL fallback."
210+
f"Expected < {SIGTERM_IGNORING_PROCESS_TIMEOUT} seconds. "
211+
"This suggests the cleanup needs SIGINT/SIGKILL fallback."
206212
)
207213
except (TimeoutError, Exception) as e:
208214
if isinstance(e, TimeoutError) or "timed out" in str(e):
209215
pytest.fail(
210-
"stdio_client cleanup timed out after 5.0 seconds with SIGTERM-ignoring process. "
216+
f"stdio_client cleanup timed out after {SIGTERM_IGNORING_PROCESS_TIMEOUT} seconds "
217+
"with SIGTERM-ignoring process. "
211218
"This confirms the cleanup needs SIGINT/SIGKILL fallback for processes that ignore SIGTERM."
212219
)
213220
else:

0 commit comments

Comments
 (0)