fix(ide-workspace): mark the registry mutation at the publisher, not at a URL (#6654) - #6655
Merged
Merged
Conversation
…at a URL (#6654) #6641 taught the synchronization pass to defer artefact cleanup while the registry is half-written, but fed RegistryMutationTracker from a servlet filter mapped on the publisher and workspace endpoints. Every other publish path stayed invisible - and the most frequent publisher in practice is not that endpoint: the model-to-code generation service publishes through the JS `lifecycle` API from a `/services/js/...` URL the filter never sees. So the race survived. On PR #6644's smoke leg, whose build already contained #6641, a scheduled pass walked into a 680 ms hole opened by service-generate/generate.mjs, removed six client-Java artefacts whose sources were about to be copied back, and the batch compile that followed produced ZERO class files ("package gen.invoices.data.invoice does not exist", then a cascade of "cannot find symbol"). No controller was registered, and IntentCrossModelFieldRetirementIT's 60s poll saw only 404s. The same window is the likeliest explanation for IntentEmissionCoverageIT reading a null status two seconds after a republish on PR #6647's leg. PublisherService now brackets its own writes, so every caller is covered by construction - the HTTP endpoint, the JS lifecycle API, generated client Java and tests alike. Enumerating callers was the bug; the service is the one place they all funnel through, and the tracker's javadoc now says so, because the next publish path must bracket its write rather than add a mapping. SynchronizerCleanupRaceIT gains the contract test: a publish that never touches the HTTP layer must still register as a completed registry mutation, since that count is exactly what the pass compares. It fails on master at that assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Fixes #6654. Follow-up to #6641, which closed one instance of this race and left a second one open.
Why it survived #6641
#6641 taught
SynchronizationProcessorto defer artefact cleanup while the registry is half-written, andJavaSynchronizernot to compile a batch it knows is incomplete. Both askRegistryMutationTracker— but the tracker was fed only by a servlet filter mapped on/services/ide/publisher/**and the workspace endpoints. Every other publish path stayed invisible, and the most frequent publisher in practice is not that endpoint:Served from
/services/js/..., so the filter never runs.What that cost
From PR #6644's smoke leg — a build that already contained #6641 (verified:
b5021cfis an ancestor of the run's head):Client Java compiles all-or-nothing, so zero class files, no controllers registered, and
IntentCrossModelFieldRetirementIT's 60 s poll saw only 404s. That was the last batch of the run — nothing repaired it. The same window is the likeliest explanation forIntentEmissionCoverageITreading"Status": nulltwo seconds after a republish on #6647's leg.The fix
PublisherServicebrackets its own writes withenter()/exit(). Every caller is then covered by construction — the HTTP endpoint, the JSlifecycleAPI, generated client Java, tests. Enumerating callers was the bug; the service is the one place they all funnel through. The tracker's javadoc now states this, so the next publish path brackets its write instead of adding a mapping.The bracket sits on
publishResource/unpublishResource— the methods that actually touch the repository — so a disabled publish or a no-op path does not register a phantom mutation.I deliberately left
RegistryMutationFilterin place: it is now redundant for the publisher endpoint, and its workspace mapping was never about the registry, but removing it is a behaviour change beyond this bug. Worth a separate look.Verification
SynchronizerCleanupRaceITgains a contract test: a publish that never touches the HTTP layer must still register as a completed registry mutation, because that count is exactly what the pass compares. Verified red-first — stashed the fix, reinstalled, and it fails precisely at that assertion; restored, and both tests pass (30 s).IntentCrossModelFieldRetirementIT— the IT this race broke — green locally.core-base/core-initializers/ide-workspacegreen;formatter:validateand the release-profile javadoc clean.Not claimed: neither CI failure was ever locally reproducible, so this fixes the mechanism the logs show rather than reproducing those two runs. The secondary point in #6654 stands separately — a batch that compiles to zero class files is not necessarily retried, so a missed hole is still a permanent outage rather than a transient one.
🤖 Generated with Claude Code