Skip to content

fix(adapter-java): tag debuggee JVMs and reap orphans on startup - #63

Merged
debugmcpdev merged 2 commits into
mainfrom
fix/java-jdwp-orphan-cleanup
Apr 30, 2026
Merged

debugmcpdev merged 2 commits into
mainfrom
fix/java-jdwp-orphan-cleanup

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

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 a Runtime shutdown hook in JdiDapServer for graceful teardown, and reap orphans from prior crashed runs at mcp-debugger startup. Cross-platform: /proc on Linux, ps -ww on macOS, Get-CimInstance on Windows.
  • 359fdc32 — follow-up fix uncovered while testing the first one: drop the stdio-silencer's stdin mirror. The silencer's preload-time process.stdin.on('data', ...) listener was racing the MCP SDK's StdioServerTransport and consuming the client's initialize request before the SDK could attach its own listener. The race had been latent since the silencer was introduced; the reaper's added await fs.readdir('/proc') in main() yielded the event loop just long enough to flip it, breaking tests/e2e/docker/docker-smoke-{javascript,python}.test.ts reproducibly with MCP error -32001: Request timed out.

The stdout mirror in the silencer is left intact — it wraps write rather than touching stream mode, so it doesn't have the same hazard.

Test plan

  • tests/unit/utils/jvm-orphan-reaper.test.ts — 8/8 passing
  • tests/e2e/docker/docker-smoke-javascript.test.ts + docker-smoke-python.test.ts — 8/8 passing in ~50 s
  • Full suite — 167 files / 2252 tests passing, 3 files / 8 tests skipped (rust docker gated by env, stress tests gated)
  • Java integration tests cover both the launch-mode shutdown hook and the reap-on-startup recovery path
  • CI green on this branch

🤖 Generated with Claude Code

cynarlab and others added 2 commits April 29, 2026 23:54
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

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 74 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/utils/jvm-orphan-reaper.ts 48.41% 65 Missing ⚠️
packages/adapter-java/src/utils/jdi-resolver.ts 41.66% 7 Missing ⚠️
src/index.ts 66.66% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@debugmcpdev
debugmcpdev merged commit c282497 into main Apr 30, 2026
8 of 9 checks passed
@debugmcpdev
debugmcpdev deleted the fix/java-jdwp-orphan-cleanup branch August 5, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants