Skip to content

Handle non-existent root directory in PhysicalFilesWatcher#126411

Merged
svick merged 17 commits intodotnet:mainfrom
svick:handle-non-existent-directory-watcher
Apr 9, 2026
Merged

Handle non-existent root directory in PhysicalFilesWatcher#126411
svick merged 17 commits intodotnet:mainfrom
svick:handle-non-existent-directory-watcher

Conversation

@svick
Copy link
Copy Markdown
Member

@svick svick commented Apr 1, 2026

Handle non-existent root directory in PhysicalFilesWatcher

Fixes #116713
Fixes #107700

Problem

When PhysicalFileProvider is constructed with a root directory that does not yet exist (e.g., a configuration file path whose parent directory hasn't been created), Watch() fails because FileSystemWatcher cannot watch a non-existent directory. This commonly occurs with AddJsonFile when the config file's parent directory is missing at startup.

FileConfigurationSource had its own solution: watching the closest existing directory instead. The problem with this is that it commonly watched too much of the system (possibly even all of it), causing significant performance issues.

Solution

PhysicalFilesWatcher now gracefully handles a missing root directory by deferring FileSystemWatcher activation until the root appears. A PendingCreationWatcher monitors the nearest existing ancestor directory using a non-recursive FileSystemWatcher and cascades through intermediate directory levels as they are created. Once the root directory exists, the main recursive FileSystemWatcher is enabled and any already-existing watched entries are reported.

Callers always receive normal FSW-backed change tokens — no re-registration is needed when the root directory appears later.

FileConfigurationSource then uses PhysicalFilesWatcher on a directory that may not exist, which is now handled well.

Changes

File Description
PhysicalFilesWatcher.cs Added PendingCreationWatcher inner class that watches for a non-existent directory to be created. TryEnableFileSystemWatcher defers to EnsureRootCreationWatcher when _root doesn't exist, with a callback to retry once it appears. ReportExistingWatchedEntries fires tokens for entries created before the FSW was active. The constructor normalizes _root to always have a trailing separator and validates FSW path relationship. OnFileSystemEntryChange now uses DirectoryInfo for directory paths so exclusion filters work correctly, and guards against events outside _root. OnError now also notifies wildcard tokens.
PhysicalFileProvider.cs Constructor no longer throws DirectoryNotFoundException for a missing root. Updated Watch doc comments to include directories. Removed duplicate _pathSeparators field in favor of PathUtils.PathSeparators.
FileConfigurationSource.cs ResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.cs GetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.cs PathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.cs Tests for missing root (file path and wildcard), root deleted and recreated, subdirectory create/delete/recreate cycles, directory watch tokens, hidden directory exclusion, FSW path above/below root, sibling directory prefix isolation, and active polling variants.
FileConfigurationProviderTest.cs Integration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.cs Added test for constructing with non-existent root. Fixed TokenFiredForGlobbingPatternsPointingToSubDirectory path length issue on .NET Framework.

When PhysicalFileProvider is constructed with a root directory that does not
yet exist, PhysicalFilesWatcher now defers FileSystemWatcher activation until
the root appears. A PendingCreationWatcher monitors the nearest existing
ancestor using a non-recursive FileSystemWatcher and cascades through
intermediate directory levels as they are created. Once the root directory
exists, the main recursive FileSystemWatcher is enabled and any
already-existing watched entries are reported.

Callers always receive normal FSW-backed change tokens — no re-registration
is needed when the root directory appears later.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 1, 2026 15:09
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-filesystem
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Microsoft.Extensions.FileProviders.Physical watcher stack (and related configuration/file-globbing components) to handle roots that don’t exist yet by deferring FileSystemWatcher activation until the directory appears, avoiding broad/incorrect ancestor watching and improving correctness for configuration reload-on-change scenarios.

Changes:

  • Add deferred root-creation monitoring to PhysicalFilesWatcher (including a cascading non-recursive watcher for intermediate directories) and report already-existing entries once the root appears.
  • Relax PhysicalFileProvider to allow construction with missing roots; update configuration source resolution to use the file’s immediate parent directory even when missing.
  • Extend polling and globbing helpers/tests to correctly handle directory paths and missing-directory lifecycles.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.cs Refresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs Defer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.cs Treat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs Allow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.cs Expose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx Add new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.cs Resolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.cs Add/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs Add test ensuring non-existent roots are accepted; update related watcher tests for robustness.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/Microsoft.Extensions.Configuration.FileExtensions.Tests.csproj Include common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.cs Integration test verifying token fires when file appears under previously-missing directory.

Copilot AI review requested due to automatic review settings April 1, 2026 15:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings April 2, 2026 10:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

@svick svick marked this pull request as ready for review April 2, 2026 12:01
Copilot AI review requested due to automatic review settings April 2, 2026 12:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings April 7, 2026 14:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Copy link
Copy Markdown
Member

@rosebyte rosebyte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jus a couple comments.

Copilot AI review requested due to automatic review settings April 9, 2026 09:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

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

Projects

None yet

4 participants