Skip to content

Commit 15e148c

Browse files
Add regression test for SIGINT cleanup handling
NOTE: this test FAIL without #555 This test verifies that stdio_client properly handles processes that ignore SIGTERM but respond to SIGINT, preventing hanging issues with Node.js servers and other interactive tools that expect Ctrl+C signals.
1 parent c75fd38 commit 15e148c

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

tests/client/test_stdio.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import shutil
22
import sys
3+
import textwrap
34
import time
45

6+
import anyio
57
import pytest
68

79
from mcp.client.session import ClientSession
@@ -178,3 +180,67 @@ async def test_stdio_client_immediate_completion():
178180
elapsed = end_time - start_time
179181
print(f"❌ Test failed after {elapsed:.1f} seconds: {e}")
180182
raise
183+
184+
185+
@pytest.mark.anyio
186+
@pytest.mark.skipif(sys.platform == "win32", reason="Windows signal handling is different")
187+
async def test_stdio_client_sigint_only_process():
188+
"""
189+
Test cleanup with a process that ignores SIGTERM but responds to SIGINT.
190+
191+
This tests Cristipufu's concern: processes that expect SIGINT (like many
192+
Node.js servers or interactive tools) may not respond to SIGTERM, causing
193+
hanging during cleanup.
194+
"""
195+
# Create a Python script that ignores SIGTERM but handles SIGINT
196+
script_content = textwrap.dedent(
197+
"""
198+
import signal
199+
import sys
200+
import time
201+
202+
# Ignore SIGTERM (what process.terminate() sends)
203+
signal.signal(signal.SIGTERM, signal.SIG_IGN)
204+
205+
# Handle SIGINT (Ctrl+C signal) by exiting cleanly
206+
def sigint_handler(signum, frame):
207+
sys.exit(0)
208+
209+
signal.signal(signal.SIGINT, sigint_handler)
210+
211+
# Keep running until SIGINT received
212+
while True:
213+
time.sleep(0.1)
214+
"""
215+
)
216+
217+
server_params = StdioServerParameters(
218+
command="python",
219+
args=["-c", script_content],
220+
)
221+
222+
start_time = time.time()
223+
224+
try:
225+
# Use anyio timeout to prevent test from hanging forever
226+
with anyio.fail_after(5.0):
227+
async with stdio_client(server_params) as (read_stream, write_stream):
228+
# Let the process start and begin ignoring SIGTERM
229+
await anyio.sleep(0.5)
230+
# Exit context triggers cleanup - this should not hang
231+
pass
232+
233+
end_time = time.time()
234+
elapsed = end_time - start_time
235+
236+
# Should complete quickly even with SIGTERM-ignoring process
237+
# This will fail if cleanup only uses process.terminate() without fallback
238+
assert elapsed < 5.0, (
239+
f"stdio_client cleanup took {elapsed:.1f} seconds with SIGTERM-ignoring process. "
240+
f"Expected < 5.0 seconds. This suggests the cleanup needs SIGINT/SIGKILL fallback."
241+
)
242+
except TimeoutError:
243+
pytest.fail(
244+
"stdio_client cleanup timed out after 5.0 seconds with SIGTERM-ignoring process. "
245+
"This confirms the cleanup needs SIGINT/SIGKILL fallback for processes that ignore SIGTERM."
246+
)

0 commit comments

Comments
 (0)