cmux-next: FolderChanges.events() returns once its watches are registered
Fix-forward for #26320 (squash 39d216bc), from its post-landing review, which answered FIX:
The startup race remains ā FolderChanges.swift:44,87. arm() calls source.resume(), which does not guarantee registration has completed. dispatch_source(3) explicitly documents that events during this delay are missed. A write after events() returns can therefore still disappear. Establish readiness through setRegistrationHandler before promising the watch is armed; rearming needs the same guarantee.
The test remains timing-dependent ā FolderChangesTests.swift:27ā32. [...] Add a deterministic readiness test with delayed registration.
What changes
FolderChanges.events()is nowasync.arm()gives every dispatch source a registration handler and waits for each one before it returns, so a change made afterevents()returns is always delivered. The wait ends early if the calling task is cancelled, so a cancelled caller never hangs.- When the watch has to move, the loop waits for the new watches to register before it cancels the old ones and yields the element, so re-arming has the same guarantee.
BackdropCatalogStore(the only production caller) awaitsevents().- If the caller is cancelled during that wait,
events()cancels the watch and returns a finished stream. The detached loop never holds descriptors for a caller that will not iterate. - New test
FolderChangesTests.eventsReturnsOnlyOnceTheWatchesAreRegistered. An internal init sets the queue the sources target. The test holds that queue suspended so no registration handler can run, and startsevents()withTask.immediate, so it runs synchronously up to its first suspension. It checks two things:events()returns only after the queue is resumed (from an ordered step log), and a write made the moment it returns produces an element. There is no sleep. With the fix the test cannot fail, however the threads are scheduled. Without the wait,events()has no suspension point and returns before the test resumes the queue, so the test fails every time. - Kept from the earlier review: there are no new file descriptors or sources. Cancel handlers still close every fd, and cancellation still reaches
armed.cancel().
No capture: no visible UI change; this only changes when the folder watch behind the wallpaper catalog reports that it is ready.
Risk
Low. The change is contained to the folder watch behind the wallpaper catalog. events() now waits for kernel registration before returning, which is sub-millisecond.
Verification
Fleet runs pending; this section is updated with their results.
Agent session: agent session
š¤ Generated with Claude Code