Skip to content

fix(filewatch): debounce raw OS events and serve the settled file#58

Merged
phil-scott-78 merged 1 commit into
mainfrom
fix/filewatcher-debounce
Jun 24, 2026
Merged

fix(filewatch): debounce raw OS events and serve the settled file#58
phil-scott-78 merged 1 commit into
mainfrom
fix/filewatcher-debounce

Conversation

@phil-scott-78

Copy link
Copy Markdown
Contributor

What

Makes the file-watch path deliver settled files, and serves the resulting cached parse instead of re-reading from disk. Three related changes on the watch path:

1. Trailing-edge debounce in FileWatcher

Replaces the old leading-edge coalescing with a trailing-edge debounce. A single save fans out into a truncate/write/close burst (plus the duplicate events Windows raises); these now collapse into one notification fired only after the file has been quiet for the debounce window. By then the editor has closed the handle, so every subscriber that re-reads the file (each IFileWatchAware cache, the live-reload server) observes the finished file instead of one mid-write — no sharing violations, no momentarily-truncated bodies. TimeProvider is injected so the debounce is testable.

2. Serve the cached body from MarkdownContentServiceMarkdownContentParser

Because the watcher now only delivers a change after the writer has closed, MarkdownContentService.DiscoverAsync can carry its already-parsed body on each DiscoveredItem (new RawBody), and MarkdownContentParser serves it directly. This skips a redundant per-request disk read + re-parse that could otherwise observe a half-written file. Falls back to reading the file for sources that don't pre-parse a body.

3. Guard the watch path against ExcludePaths subtrees

On edit, MarkdownContentService now skips files inside an ExcludePaths subtree (IsExcludedAbsolute), matching what DiscoverFiles already does at discovery time — so a source that carves out a subtree for another source can't adopt an excluded file and publish a route that competes with the owning source's.

Test plan

  • dotnet build Pennington.slnx — whole solution compiles (covers the FileWatcher constructor-signature change and the RawBody addition across all consumers).
  • dotnet test tests/Pennington.Tests1037 passed, 0 failed, including new tests:
    • FileWatcher_CoalescesRapidBurst_IntoSingleNotification
    • ParseAsync_DiscoveredItemCarriesCachedBody_ServesCacheWithoutReadingDisk
    • ParseAsync_CachedBodyWithoutMetadata_FallsBackToDisk
    • OnFileChanged_Changed_ExcludedSubtree_NotCached

Branched off main; contains no blog/content edits.

Rework FileWatcher coalescing into a trailing-edge debounce: a file's
burst of raw OS events (truncate/write/close plus the Windows duplicate
events) now collapses into a single notification fired only after the
file has been quiet for the debounce window. By then the editor has
closed the handle, so every subscriber that re-reads the file observes
the finished file instead of one mid-write. Injects TimeProvider so the
debounce is testable.

Because the watcher now delivers a change only after the writer has
closed, MarkdownContentService can carry its cached body on each
DiscoveredItem (RawBody) and MarkdownContentParser serves it directly,
skipping a redundant per-request disk read + re-parse that could
otherwise observe a half-written file.

Also guard the file-watch path against adopting an ExcludePaths subtree
on edit (IsExcludedAbsolute), matching DiscoverFiles, so a source that
carves out a subtree for another source can't publish a competing route.
@github-actions

Copy link
Copy Markdown

🛰️ Docs preview: https://pr-58.pennington-dev.pages.dev

Rebuilt on every push to this PR; torn down when it closes.

@phil-scott-78 phil-scott-78 merged commit 148b895 into main Jun 24, 2026
4 checks passed
@phil-scott-78 phil-scott-78 deleted the fix/filewatcher-debounce branch June 24, 2026 23:12
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.

1 participant