Skip to content

mcp serve zombies survive client death; orphan watchdog and EOF shutdown are ineffective under mcp 2.0 #2325

Description

@Q00

Symptom (production, 2026-08-31)

  • 119 `ouroboros mcp serve` processes on one machine; 54 holding `~/.ouroboros/data/ouroboros.db` open → SQLite write-lock contention → per-process `QueuePool limit of size 5 overflow 10 reached` (714 occurrences) → run failures.
  • Zombies' fd 0/1/2 are unix sockets with a dead peer (`->(none)` in lsof): the client died, the server never exits. Reproduced nightly (29 zombies from one evening).
  • One streamable-http serve orphaned to launchd ran 5 days at 40–80% CPU (4,263 CPU-minutes).

Root causes (audited on main @ v0.52.0)

  1. The shutdown EOF trick `os.close(0)` (cli/commands/mcp.py:978-981) is a no-op under mcp==2.0.0: the SDK diverts fd 0 to /dev/null and reads the wire from a private dup'd fd (mcp/server/stdio.py).
  2. The stdin readline runs in a shielded, non-daemon anyio worker thread; even after the serve coroutine returns, the interpreter blocks forever in threading shutdown. `os._exit` is explicitly forbidden (mcp.py:967-969), so there is no escape hatch.
  3. `_orphan_watchdog` disables itself entirely when `orig_ppid == 1` (mcp.py:873-874) — a launchd-orphaned server has no lifecycle tether at all. streamable-http additionally has no idle shutdown.
  4. CPU runaway: `JobManager._monitor_job` polls at 1 Hz and `get_snapshot` replays the job's events from row 0 on every call (mcp/job_manager.py:888, 1970), with no backoff.

Fix plan

  • PR A: hard-exit backstop after the shutdown grace period + dead-peer (MSG_PEEK) and `getppid()==1` detection in the watchdog.
  • PR B: idle shutdown for streamable-http + incremental cursor / backoff for the job monitor loops.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-designMulti-PR epic or architectural change, needs human planning

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions