PrefectHQ/fastmcp · error · RuntimeError
Server process failed to terminate even after kill
Error message
Server process failed to terminate even after kill
What it means
After the test server subprocess fails to terminate gracefully (proc.terminate + join(5s)), the helper escalates to proc.kill() and waits 2 more seconds; if the process is still alive it raises RuntimeError. This guards tests against hanging on unkillable subprocesses.
Source
Thrown at fastmcp_slim/fastmcp/utilities/tests.py:151
time.sleep(0.05)
elif attempt < 15:
time.sleep(0.1)
else:
time.sleep(0.2)
attempt += 1
else:
raise RuntimeError(f"Server failed to start after {max_attempts} attempts")
yield f"http://{host}:{port}"
proc.terminate()
proc.join(timeout=5)
if proc.is_alive():
# If it's still alive, then force kill it
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
raise RuntimeError("Server process failed to terminate even after kill")
async def _wait_for_port(host: str, port: int, timeout: float = 5.0) -> None:
"""Poll until a TCP connection to `host:port` is accepted, or raise on timeout."""
deadline = time.monotonic() + timeout
while True:
try:
_, writer = await asyncio.open_connection(host, port)
except (ConnectionRefusedError, OSError):
if time.monotonic() >= deadline:
raise RuntimeError(
f"Server did not start listening on {host}:{port} "
f"within {timeout} seconds"
) from None
await asyncio.sleep(0.001)
else:
writer.close()
with suppress(ConnectionResetError, BrokenPipeError):View on GitHub (pinned to 1f02114297)
Solutions
- Ensure the server app has no long-running non-daemon children that block exit
- Check the OS process table (ps) for a stuck/uninterruptible process and clean it up
- Run tests on a different platform/CI image if the spawn method is the cause
- Increase the join timeouts in the helper if termination is merely slow
Defensive patterns
Strategy: try-catch
Try / catch
try:
with run_server_in_process(mcp_server) as url:
yield url
except RuntimeError as e:
if "failed to terminate" in str(e):
psutil.cleanup_processes() # or inspect/kill stragglers manually
else:
raise Prevention
- Avoid spawning non-daemon child processes inside the server under test
- Use daemon processes/threads in test server apps
- Check for zombie processes between test runs
- Keep join timeouts generous on loaded CI machines
When it happens
Trigger: The server process ignores SIGTERM and SIGKILL-equivalent termination — typically due to blocked/uninterruptible state, child processes keeping resources, or platform quirks (e.g. Windows multiprocessing semantics) preventing clean join.
Common situations: Servers spawning their own children that inherit the pipe; processes stuck in uninterruptible disk I/O in CI; multiprocessing issues on macOS spawn method; zombie process accumulation in long test sessions.
Related errors
- Server failed to start after {max_attempts} attempts
- Could not restrict access to CLI state
- Failed to initialize project: {e.stderr}
- Failed to pin Python version: {e.stderr}
- Invalid transport: {transport}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/83bde7a3c8fe4071.
Report an issue: GitHub.