Skip to content

Add ways for server processes to self-terminate when host is gone #208

Open

Description

@jrieken

While working on VS Code and LSP we have learned that it is quite hard to prevent leaking processes, e.g stdio servers might be lingering around long after the host is gone. This can be due to various reasons and an effective way we have found is to start a server with the PID of its host. With that the server periodically checks for the PID and terminates if it is gone, like so https://github.com/microsoft/vscode-languageserver-node/blob/main/server/src/node/main.ts#L51

Having something like this in the SDK would benefit MCP servers and help to keep process-bleed in check

Activity

  1. added
    enhancementRequest for a new feature that's not currently supported
    on Mar 19, 2025
  2. added a commit that references this issue on May 2, 2025
  3. added
    help wantedContributions especially welcome if you have a good knowledge of the codebase and language
    P3Nice to haves, rare edge cases
    on Dec 16, 2025
  4. added a commit that references this issue on Mar 19, 2026
    e253410
  5. windro-xdd commented on Mar 29, 2026

    @windro-xdd

    I'd like to take this one. Planning to add a parent PID check similar to the VS Code LSP approach mentioned — server periodically checks if the host PID is alive and gracefully terminates if it's gone. Will submit a PR soon.

  6. added a commit that references this issue on Apr 2, 2026
    7d89648
  7. ghost added
    needs decisionIssue is actionable, needs maintainer decision on whether to implement
    on Apr 17, 2026
  8. ettalin commented on Sep 3, 2026

    @ettalin

    Three PRs have tried to land here (#1572, #1815, #1712) and the issue is still
    labelled needs decision, so rather than propose a fourth implementation I want
    to offer data on the specific choice that seems to be blocking it.

    The choice is parent-PID polling vs. stdin EOF. #1712 takes the VS Code LSP
    approach — process.kill(hostPid, 0) on a 3s interval — which is a reasonable
    reference and works. Two properties of it are worth being explicit about before
    it becomes the SDK's default.

    Up front: my own measurements come from the client side of this problem —
    supervising spawned children rather than a server watching its host. The
    mechanism comparison transfers; the implementation doesn't, so I'm offering this
    as evidence about the two detection strategies, not as a proposal to adopt
    anything of mine.

    1. PID reuse turns a transient failure into a permanent one. If the host dies
    and its PID is recycled before the next tick, kill(pid, 0) succeeds against an
    unrelated process and the watchdog concludes the host is alive — not for one
    interval, but for the rest of the server's life. The window is small; the
    consequence is unbounded. Any poll-the-PID design needs a second signal — process
    start time, or a token passed at spawn — to be sound. Worth deciding deliberately
    rather than inheriting, since the LSP reference has the same property.

    2. Detection latency is the poll interval. At the 3s default, a host that dies
    at t=0 leaves the server running until t≤3s. That is fine if the goal is eventual
    cleanup. It is less fine if the motivation is the accumulation described in
    #2002, where short-lived sessions churn.

    stdin EOF has neither property. The kernel closes the pipe when the host dies
    — by any means, including SIGKILL — so detection is immediate, and no PID is ever
    consulted, so recycling cannot produce a false "alive".

    I built a harness for the client-side version of this and ran the EOF approach
    against the most hostile case I could construct: the parent SIGKILLed in the
    same tick as the last spawn, so no userspace cleanup can possibly run. 0 of 48
    leaked
    across 12 trials. Buffered writes still arrive, because Node fires
    'end' only after the stream drains — a registration written microseconds before
    death is processed before the reap. That is macOS / Node 24 only; I have not run
    it on Linux or Windows.

    Which raises a scoping question. If #2002 lands stdin close/end handling
    server-side, the ordinary case here is already covered — the server exits when the
    host drops the pipe, with no watchdog involved. The watchdog's remaining value is
    then the narrower case where stdin is not the host's pipe: servers launched with
    stdin redirected, or behind a wrapper that holds the pipe open.

    That is a real gap and worth solving. But it is a much smaller one than "servers
    should self-terminate when the host is gone", and it may be why this issue keeps
    stalling — three PRs have scoped it as a general mechanism while the general case
    is being solved in another thread.

    If it would help whoever picks this up, the harness is public. It runs a matrix
    over spawn shape × cleanup strategy × how the parent dies, and includes
    no-cleanup control scenarios that must always orphan — so a broken detector
    aborts the run instead of silently reporting everything as clean:
    https://github.com/ettalin/subreap

    Happy to run any proposed implementation through it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Nice to haves, rare edge casesenhancementRequest for a new feature that's not currently supportedhelp wantedContributions especially welcome if you have a good knowledge of the codebase and languageneeds decisionIssue is actionable, needs maintainer decision on whether to implement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions