Skip to content

fix(ide-workspace): mark the registry mutation at the publisher, not at a URL (#6654) - #6655

Merged
delchev merged 1 commit into
masterfrom
fix/registry-mutation-at-the-source
Aug 11, 2026
Merged

fix(ide-workspace): mark the registry mutation at the publisher, not at a URL (#6654)#6655
delchev merged 1 commit into
masterfrom
fix/registry-mutation-at-the-source

Conversation

@delchev

@delchev delchev commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

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 SynchronizationProcessor to defer artefact cleanup while the registry is half-written, and JavaSynchronizer not to compile a batch it knows is incomplete. Both ask RegistryMutationTracker — 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:

// components/ui/service-generate/.../generate.mjs
lifecycle.publish(user.getName(), workspace, project);        // line 107
lifecycle.unpublish(projectName + "/gen/" + genFolderName);   // line 195

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: b5021cf is an ancestor of the run's head):

09:06:13.905  POST /services/js/service-generate/generate.mjs/...      <- not the publisher endpoint
09:06:14.783  PublisherService - Unpublished ...                        <- hole opens
09:06:15.372  JavaSynchronizer - Removing Java artefact
              [/retire-owner/gen/invoices/api/invoice/InvoiceController.java] - its source is gone  (x6)
09:06:15.463  PublisherService - Published ...                          <- hole closes, 680 ms later
09:06:20.619  Compiled batch: [12] units, [0] class file(s); [12] failed
              (cannot find symbol: class InvoiceRepository)

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 for IntentEmissionCoverageIT reading "Status": null two seconds after a republish on #6647's leg.

The fix

PublisherService brackets its own writes with enter() / exit(). Every caller is then covered by construction — the HTTP endpoint, the JS lifecycle API, 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 RegistryMutationFilter in 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

  • SynchronizerCleanupRaceIT gains 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.
  • 34 unit tests across core-base / core-initializers / ide-workspace green; formatter:validate and 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

…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>
@delchev
delchev merged commit ef6c02a into master Aug 11, 2026
10 checks passed
@delchev
delchev deleted the fix/registry-mutation-at-the-source branch August 11, 2026 07:55
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.

Registry-write race survives #6641: a publish that does not come through the publisher endpoint is invisible to the mutation tracker

1 participant