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

  1. Ensure the server app has no long-running non-daemon children that block exit
  2. Check the OS process table (ps) for a stuck/uninterruptible process and clean it up
  3. Run tests on a different platform/CI image if the spawn method is the cause
  4. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/83bde7a3c8fe4071. Report an issue: GitHub.