Skip to content

fix(java): prevent ClassPrepareEvent from resuming stopped threads#27

Merged
debugmcpdev merged 2 commits intodebugmcp:mainfrom
Finomosec:fix/event-loop-resume-race
Mar 28, 2026
Merged

fix(java): prevent ClassPrepareEvent from resuming stopped threads#27
debugmcpdev merged 2 commits intodebugmcp:mainfrom
Finomosec:fix/event-loop-resume-race

Conversation

@Finomosec
Copy link
Copy Markdown
Contributor

Summary

  • Fixes a race condition in the JDI event loop where a ClassPrepareEvent in the same EventSet as a BreakpointEvent would override resume = false with resume = true, incorrectly resuming the stopped thread
  • This caused "Thread has been resumed" errors when calling evaluate_expression after a breakpoint hit, especially with suspendPolicy="thread"
  • Adds a stopped flag that prevents non-stopping events (ClassPrepare) from overriding the suspension decision of stopping events (Breakpoint, Step, Exception)

Root Cause

In JdiDapServer.java's event loop, events in an EventSet are processed sequentially. A BreakpointEvent sets resume = false, but a subsequent ClassPrepareEvent in the same set overwrites it with resume = true. The eventSet.resume() at the end then resumes the thread before any evaluation can occur.

Test plan

  • Added E2E test (mcp-server-smoke-java-event-race.test.ts) that:
    • Sets breakpoint with suspendPolicy="thread" in main class
    • Sets deferred breakpoint in not-yet-loaded helper class (triggers ClassPrepareRequest)
    • Verifies evaluate_expression succeeds at the breakpoint (would fail before fix)
    • Verifies second breakpoint in late-loaded class also fires correctly
  • Run existing Java smoke tests to verify no regression
  • Manual test: attach to running JVM, set breakpoint with suspendPolicy="thread", verify evaluation works

🤖 Generated with Claude Code

Finomosec and others added 2 commits March 27, 2026 13:52
… event loop

When an EventSet contains both a stopping event (breakpoint, step, exception)
and a ClassPrepareEvent, the ClassPrepareEvent's `resume = true` overwrites
the stopping event's `resume = false`. This causes the thread to be resumed
before evaluate_expression can access it, resulting in "Thread has been
resumed" errors.

Track whether a stopping event was seen in the current EventSet and only
allow ClassPrepareEvent to set resume=true if no stopping event occurred.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ondition

Adds a test that verifies evaluate_expression works when a BreakpointEvent
with suspendPolicy="thread" fires while a ClassPrepareRequest is active for
a not-yet-loaded class. Before the fix, the ClassPrepareEvent could override
the breakpoint's resume=false flag, causing "Thread has been resumed" errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@debugmcpdev debugmcpdev merged commit 507631e into debugmcp:main Mar 28, 2026
7 checks passed
@debugmcpdev
Copy link
Copy Markdown
Collaborator

Thanks for this fix, @Finomosec! The root cause analysis was spot-on, and the stopped flag approach handles all event orderings correctly. The E2E test is thorough and well-documented.

Merged as-is — nice work.

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