You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
createCLI() / program.parseAsync() run strictly after
Consequences:
They run for every invocation, including --version, --help, and bad-arg errors — not just when a server is actually starting. A --version call on a busy Linux host pays the full double /proc walk.
The two reapers walk /proc independently and concurrently — the same readdir('/proc') + per-pid cmdline read happens twice at the same time (2 × PROC_SCAN_CONCURRENCY reads in flight post-perf(reaper): bound /proc scan concurrency at startup #395), for two different marker greps over identical data.
There is also no env opt-out for containerized/ephemeral environments where orphans are impossible (fresh PID namespace) and the scan is pure waste.
Proposal
Move the reaper invocation into the server-starting command actions (stdio/http/sse) so --version/--help never pay it.
Share one /proc walk: a single scan produces (pid, argv) pairs; each reaper contributes a matcher over that list. Halves the I/O and the transient allocations.
Consider firing the reapers after the transport is up (fire-and-forget with logging) instead of blocking startup — orphan reaping is not a precondition for serving requests. Needs care re: process-lifetime races, so measure first.
Optional MCP_SKIP_ORPHAN_REAPERS=1 escape hatch for PID-namespaced containers.
Further slimming candidate, measured by the perf(reaper): bound /proc scan concurrency at startup #395 author but deferred to keep that PR's I/O model unchanged: a synchronous openSync/readSync scan with one recycled buffer (their numbers: 57 MB / 25 ms vs 69 MB / 99 ms for the bounded-async version). Requires rewriting the fs/promises mocks in the reaper tests.
Measuring
scripts/mem-bench.mjs (busy-proc scenario) synthesizes an N-process /proc inside the docker image and reports RSS + peak (VmHWM) medians across trials — use it to validate any change here. Baseline numbers are in the PR that added the harness.
Problem
The two startup orphan reapers (
reapOrphanJvms,reapOrphanProxies) are awaited at the very top ofmain(), before commander even parses argv:src/index.ts:108-111—await Promise.allSettled([reapOrphanJvms(...), reapOrphanProxies(...)])createCLI()/program.parseAsync()run strictly afterConsequences:
--version,--help, and bad-arg errors — not just when a server is actually starting. A--versioncall on a busy Linux host pays the full double/procwalk.ps/PowerShell)./procindependently and concurrently — the samereaddir('/proc')+ per-pidcmdlineread happens twice at the same time (2 ×PROC_SCAN_CONCURRENCYreads in flight post-perf(reaper): bound /proc scan concurrency at startup #395), for two different marker greps over identical data.There is also no env opt-out for containerized/ephemeral environments where orphans are impossible (fresh PID namespace) and the scan is pure waste.
Proposal
stdio/http/sse) so--version/--helpnever pay it./procwalk: a single scan produces(pid, argv)pairs; each reaper contributes a matcher over that list. Halves the I/O and the transient allocations.MCP_SKIP_ORPHAN_REAPERS=1escape hatch for PID-namespaced containers.openSync/readSyncscan with one recycled buffer (their numbers: 57 MB / 25 ms vs 69 MB / 99 ms for the bounded-async version). Requires rewriting thefs/promisesmocks in the reaper tests.Measuring
scripts/mem-bench.mjs(busy-proc scenario) synthesizes an N-process/procinside the docker image and reports RSS + peak (VmHWM) medians across trials — use it to validate any change here. Baseline numbers are in the PR that added the harness.Follow-up to #395.