Repository navigation
fix(adapter-java): tag debuggee JVMs and reap orphans on startup - #63
Merged
Merged
Conversation
After abnormal session termination, debuggee JVMs launched by
JdiDapServer with -agentlib:jdwp,suspend=y were left running, holding
TCP ports and consuming RAM. Repeated runs accumulated orphans.
Closed in two coordinated phases:
1. Graceful path: JdiDapServer registers a Runtime shutdown hook that
destroys the launched debuggee on SIGTERM, and tags every debuggee
with -Dmcp.debugger.{jvm,owner_pid,session_tag} markers. The proxy
waits 500ms in launch mode (mirroring the existing attach-mode wait)
so JdiDapServer has time to clean up before being killed itself.
2. Recovery path: A new cross-platform reaper runs at mcp-debugger
startup, enumerates tagged JVMs (Linux /proc, macOS ps -ww, Windows
Get-CimInstance) and SIGKILLs any whose owner_pid is dead.
The kill itself is Node-portable; only enumeration branches per
platform. jdi-resolver gains mtime-based cache invalidation so stale
bridge classes rebuild against the new --owner-pid arg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The stdio-silencer preloader attached a 'data' listener to process.stdin to mirror raw input to /app/logs/stdin-raw.log for diagnostics. That listener ran at preload time, before the MCP SDK's StdioServerTransport added its own 'data' listener inside server.connect(). Any bytes the client wrote into the container's stdin pipe before the SDK was ready were consumed by the silencer in flowing mode and lost; the SDK never saw the 'initialize' request, and the client timed out at 60s. The race had been latent since the silencer was introduced. The recent orphan-reaper commit (3771be6) put an early `await fs.readdir('/proc')` in front of CLI parsing, which yielded the event loop just long enough for buffered stdin bytes to drain into the silencer-only listener before transport.start() ran. That tipped the race from "barely won" to "reproducibly lost," manifesting as docker-smoke-javascript and docker-smoke-python failing with `MCP error -32001: Request timed out`. Removing the stdin mirror is the right scope: the stdout mirror is non-invasive (wraps write, doesn't touch stream mode) and stays. We lose a diagnostic file that nothing depended on. Verified: both Docker smoke suites pass (8/8, ~50s end-to-end), reaper unit tests pass (8/8), full suite is 167 files / 2252 tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch combines two related fixes:
3771be67— original fix: tag every JDI-launched debuggee JVM with-Dmcp.debugger.{jvm,owner_pid,session_tag}markers, register aRuntimeshutdown hook inJdiDapServerfor graceful teardown, and reap orphans from prior crashed runs at mcp-debugger startup. Cross-platform:/procon Linux,ps -wwon macOS,Get-CimInstanceon Windows.359fdc32— follow-up fix uncovered while testing the first one: drop the stdio-silencer's stdin mirror. The silencer's preload-timeprocess.stdin.on('data', ...)listener was racing the MCP SDK'sStdioServerTransportand consuming the client'sinitializerequest before the SDK could attach its own listener. The race had been latent since the silencer was introduced; the reaper's addedawait fs.readdir('/proc')inmain()yielded the event loop just long enough to flip it, breakingtests/e2e/docker/docker-smoke-{javascript,python}.test.tsreproducibly withMCP error -32001: Request timed out.The stdout mirror in the silencer is left intact — it wraps
writerather than touching stream mode, so it doesn't have the same hazard.Test plan
tests/unit/utils/jvm-orphan-reaper.test.ts— 8/8 passingtests/e2e/docker/docker-smoke-javascript.test.ts+docker-smoke-python.test.ts— 8/8 passing in ~50 s🤖 Generated with Claude Code