Repository navigation
Conversation
… FileSystemWatcher Root cause of the recurring CI flake in NodeHubContentCollectionTest.CollectionNamedArea_RendersBrowserForFolder_AndContentForFile (ObservableAssertionException: "did not emit within 30s"): A content collection is cached and scanned ONCE at init; SaveFileAsync then wrote to disk but did NOT update the collection's markdown stream — it relied entirely on the FileSystemWatcher to notice the new file and merge it (UpdateArticle). The file-content render reduces that stream, so if the watcher event is dropped or delayed — which it is under load; FileSystemWatcher coalesces events and overflows its OS buffer — the just-saved file never enters the stream, the render emits null, and the assertion times out at 30s. This also bit production: an upload followed by a view could show "not found" until (or unless) a watcher event arrived. Fix: SaveFileAsync merges the just-written markdown file into the stream itself (read-your-writes), via the same path the watcher uses. The watcher stays the mechanism for OUT-OF-BAND (external) changes; a save WE performed is now reflected deterministically. Proven deterministically (not a rerun): with the new DisableFileWatcher config the file can reach the stream ONLY through SaveFileAsync's merge, so the added test (SaveFileReadYourWritesTest) times out at 30s WITHOUT the fix and passes in ~370ms WITH it — reproducing and closing the exact CI failure mode. DisableFileWatcher is also a genuine option for backends where the watcher is unreliable (network mounts, high load). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a recurring CI flake (and a real production read-after-write issue) in ContentCollections by ensuring a markdown file saved via ContentCollection.SaveFileAsync is merged into the collection’s markdown stream immediately, rather than relying on potentially dropped/delayed FileSystemWatcher events.
Changes:
- Update
ContentCollection.SaveFileAsyncto proactively ingest just-written markdown files intomarkdownStream(read-your-writes). - Add
DisableFileWatchertoContentCollectionConfigto support deterministic behavior where file watching is unreliable. - Add a regression test that reproduces the prior failure mode by disabling the watcher and asserting save→render succeeds.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/MeshWeaver.ContentCollections.Test/SaveFileReadYourWritesTest.cs | Adds a regression test that disables file watching and verifies save→render works deterministically. |
| src/MeshWeaver.ContentCollections/ContentCollectionConfig.cs | Introduces DisableFileWatcher configuration with documentation explaining tradeoffs. |
| src/MeshWeaver.ContentCollections/ContentCollection.cs | Ensures SaveFileAsync merges markdown writes into the stream immediately; makes AttachMonitor a no-op when watcher is disabled. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Text; | ||
| using MeshWeaver.Data; |
|
Superseded by #238 (merged, main green — "ingest a collection's own write immediately"), which lands the same read-your-writes fix (SaveFileAsync → proactive UpdateArticle) plus a watcher Created handler and path normalization, and its own deterministic regression test (ContentCollectionWriteIngestTest). This PR's core fix is now redundant and would conflict on merge. Independent same-root-cause confirmation: I reproduced the failure deterministically by disabling the FileSystemWatcher (Linux inotify drops the create event for a file written into a just-created subdir; only the FILE-content render — which reduces the watcher-populated markdown stream — flaked; the folder listing reads the provider live, so it was never affected; CI-only because macOS FSEvents doesn't miss). The only unique bit here is the optional |
|
Superseded by #238, which merged first and is live on main (green): |
The recurring CI flake, root-caused and killed deterministically
NodeHubContentCollectionTest.CollectionNamedArea_RendersBrowserForFolder_AndContentForFilefails intermittently on shard 2 withObservableAssertionException : Expected the observable to emit a value matching the predicate within 30s, but it did not— it's been rerun-once past the merge gate repeatedly (including on #224 and #239 in this session).Root cause (an "event miss")
A content collection is cached and its markdown files scanned once at init.
ContentCollection.SaveFileAsyncwrote the file to disk but did not update the collection's markdown stream — it relied entirely on theFileSystemWatcherto notice the new file and merge it (UpdateArticle). The file-content render (GetMarkdown→ reduce that stream) therefore depends on a watcher event that is asynchronous and lossy:FileSystemWatchercoalesces events and overflows its OS buffer under load. When the event is dropped or delayed, the just-saved file never enters the stream → the render emits null → the 30s assertion times out.This also bit production: an upload immediately followed by a view could show "not found" until (or unless) a watcher event arrived.
Fix
SaveFileAsyncnow merges the just-written markdown file into the stream itself (read-your-writes), through the same path the watcher uses. The watcher remains the mechanism for out-of-band (external) changes; a save the process performed is reflected deterministically.Proven deterministically — both directions
The new
SaveFileReadYourWritesTestruns the same save→render with the watcher turned off (via a new, genuinely-usefulDisableFileWatcherconfig — the watcher is unreliable on network mounts / under load), so the file can reach the stream only throughSaveFileAsync's merge:All 16 tests in the project pass; Release
-warnaserror -p:CIRun=trueclean.🤖 Generated with Claude Code