fix(server): replace reassignable readyLatch with a fixed monitor to close TOCTOU race #90
No reviewers
Labels
No labels
bug
build
enhancement
headless
P1-critical
P2-high
P3-medium
P4-low
refactoring
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ai-tools/jdt-mcp-server!90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/48-readylatch-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
HeadlessApplication.readyLatchwas avolatile CountDownLatchreassigned inreloadWorkspace(). A thread callingawaitReady()could read the field, geta stale (already counted-down) latch instance right as
reloadWorkspace()swapped in a fresh one, and return immediately — even though import/build
was still in progress.
reloadWorkspace()itself was also not guardedagainst concurrent invocations.
Fix
CountDownLatchwith a single, never-reassignedmonitor guarding a plain
readyboolean.isImporting(),awaitReady()and the new private
setReady()all synchronize on the same monitor, sothere is no stale reference to read — a thread always observes the
current state.
reloadWorkspace()via aReentrantLock: Eclipse's workspacedoes not tolerate overlapping project remove/import/build sequences, so
concurrent reload calls now block on each other instead of interleaving.
isImporting(),awaitReady(long, TimeUnit)andreloadWorkspace()are unchanged — no changes needed inMcpProtocolHandlerorProjectInfoTools.Verification
mvn -pl org.naturzukunft.jdt.mcp -am compile— clean.mvn clean package— product build succeeds.tests/smoke-test.shagainst the built product: 9/9 passed.tests/lifecycle-test.shagainst the built product: 5/5 passed.tests/fixtures/fixture-parentover stdio: firedjdt_reload_workspaceand immediately fired a non-import-safe tool call(
jdt_get_compilation_errors); the second call's response only arrivedafter
"Reload workspace finished — ready for requests"was logged.Note: stdio uses a single reader thread that processes requests strictly
sequentially, so this confirms readiness-gating still works end-to-end but
cannot reproduce the original two-thread race (background import daemon vs.
a concurrent reload) in a deterministic test; the fix's correctness for that
case rests on there no longer being a swappable reference to read stale,
not on this timing-dependent reproduction.
Closes #48