Skip to content

LocalRegistryWatcher watches the whole registry recursively and notifies nobody - LocalRegistryWatcherHandler has zero implementations #7303

Description

@iliyan-velichkov

Summary

LocalRegistryWatcher (components/core/core-registry/.../watcher/LocalRegistryWatcher.java) watches
/registry/public recursively, registers directories created after startup, and fans every change
out to List<LocalRegistryWatcherHandler>. That interface has zero implementations — in the whole
repository, production and test alike:

$ grep -rn "LocalRegistryWatcherHandler" --include="*.java" . | grep -v /target/
components/core/core-registry/.../LocalRegistryWatcherHandler.java   # the interface
components/core/core-registry/.../LocalRegistryWatcher.java          # the field, the ctor, six fan-out loops

So every instance starts a thread, opens a WatchService, walks the entire registry once
(Performing initial sync... / Initial sync complete.), and keeps polling it — to hand every event
to an empty list. It has been like that since it was added in #5666 (2026-02-09), together with a
configuration key, DIRIGIBLE_REGISTRY_LOCAL_IGNORED_FOLDERS, that consequently cannot affect anything
observable either.

Why this is worth an issue rather than a deletion PR

Because the capability it would provide is exactly the one the platform is missing, and its absence
has already cost a production defect.

SynchronizationWatcher (core-base) — the thing that decides whether a synchronization pass runs at
all — registers only the registry root, non-recursively:

Path path = Paths.get(folder);
path.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

A file written several folders deep therefore produces no event on Linux, isSynchronizationNeeded()
keeps answering false, and no pass ever runs. A publish is fine (SynchronizationWatcherPublisherHandler
forces one), but anything else that writes the registry is invisible. That is the root cause of #7192:
the external-folder copy wrote deep into the registry, nothing was scheduled, and a client-Java
generation that lost the race against the copy stayed incomplete for the life of the process —
controllers answering 404 with all their sources on disk. PR #7299 fixes it at the writer
(RecursiveFolderWatcher now brackets RegistryMutationTracker and calls SynchronizationWatcher.force()),
deliberately not at the watcher, to keep the blast radius on the feature that had the defect.

So we have a recursive registry watcher that notifies nobody, sitting next to a shallow registry watcher
that everything depends on. The two facts belong together.

What to decide

Three options, in increasing order of ambition:

  1. Delete LocalRegistryWatcher + LocalRegistryWatcherHandler + DIRIGIBLE_REGISTRY_LOCAL_IGNORED_FOLDERS.
    Nothing consumes them; this reclaims a thread, a watch service and a full registry walk per boot. The
    class also carries real hard-won machinery (the macOS PollingWatchService close deadlock handled in
    closeOffThread, guarded by LocalRegistryWatcherShutdownTest) which would have to be kept — PR engine-java, core-registry: an external-folder publish schedules a pass, and a broken source no longer zeroes the batch (#7192) #7299
    already reuses closeOffThread from RecursiveFolderWatcher, so it cannot simply go with the class.

  2. Wire one handler that forces a synchronization pass, making any deep registry write self-healing
    rather than only the external-folder one. This is what the SPI looks designed for. It changes behaviour
    on every deployment and every integration test, which is why engine-java, core-registry: an external-folder publish schedules a pass, and a broken source no longer zeroes the batch (#7192) #7299 did not do it — it needs its own
    measurement (how often does a pass get scheduled that would not have been?) and its own review.

  3. Make SynchronizationWatcher itself recursive and drop LocalRegistryWatcher — one watcher instead
    of two. Broadest, and the riskiest: it needs dynamic registration of new directories, and on a platform
    with no native file-event source it is a polling watcher over the whole registry.

My preference is (2) or (1); (3) puts a polling walk of the entire registry on the critical path for the
one platform where the JDK has no native watcher.

Notes for whoever takes it

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions