Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Handle non-existent root directory in PhysicalFilesWatcher - #126411

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

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

Conversation

@svick

@svicksvick commented Apr 1, 2026

Copy link
Copy Markdown
Member

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

FileDescription
PhysicalFilesWatcher.csAdded 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.csConstructor 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.csResolveFileProvider creates the PhysicalFileProvider with the file's immediate parent directory (even if missing), relying on the watcher to handle the non-existent case.
PollingFileChangeToken.csGetLastWriteTimeUtc now falls back to checking DirectoryInfo when FileInfo.Exists is false, so polling correctly detects directory changes. DirectoryInfo is created lazily.
PathUtils.csPathSeparators made internal for reuse across files.
PhysicalFilesWatcherTests.csTests 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.csIntegration test verifying the watch token fires when a file is created inside a previously-missing directory.
PhysicalFileProviderTests.csAdded 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>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Abstractions/DirectoryInfoWrapper.csRefresh DirectoryInfo before checking Exists to avoid stale existence state.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.csDefer FSW enablement until root exists via PendingCreationWatcher, add gap-coverage scan, adjust filtering and error handling.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.csTreat watched path as file or directory (directory fallback) so polling detects directory changes.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.csAllow missing root at construction; centralize separator trimming via PathUtils.PathSeparators; adjust watcher creation.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.csExpose PathSeparators for reuse and make trailing-slash normalization accept both separators.
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resxAdd new resource strings for missing-root and invalid FSW-path diagnostics.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.csResolve provider to the file’s immediate directory (even if missing) and reduce Path to the filename.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.csAdd/adjust tests covering missing roots, root recreation, directory tokens, exclusion behavior, and polling variants.
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.csAdd 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.csprojInclude common TempDirectory helper for new test coverage.
src/libraries/Microsoft.Extensions.Configuration.FileExtensions/tests/FileConfigurationProviderTest.csIntegration test verifying token fires when file appears under previously-missing directory.

CopilotAI review requested due to automatic review settings April 1, 2026 15:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI review requested due to automatic review settings April 2, 2026 10:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
CopilotAI review requested due to automatic review settings April 2, 2026 12:01

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@rosebyterosebyte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Jus a couple comments.

CopilotAI review requested due to automatic review settings April 9, 2026 09:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

@svick
svick merged commit b9ef488 into dotnet:mainApr 9, 2026
84 of 94 checks passed
@svick
svick deleted the handle-non-existent-directory-watcher branch April 9, 2026 13:15
adamsitnik pushed a commit that referenced this pull request Apr 15, 2026
> [!NOTE]
> This PR was AI/Copilot-generated.
Fixes#126787
## Problem
The
`ResolveFileProvider_WithMissingParentDirectory_WatchTokenFiresWhenFileCreated`
test introduced in #126411 is flaky on the `net481-windows-Release-x86`
CI leg, causing it to time out and block CI.
## Root cause
`FileSystemWatcher` on .NET Framework drops directory creation events
under I/O contention. The `PendingCreationWatcher` in
`PhysicalFilesWatcher` relies on FSW to detect when a missing root
directory is created. When the FSW event is lost, the change token never
fires and the test hangs.
This was verified by:
1. **Reproducing locally** — running the test in parallel (4-8
concurrent `dotnet test` processes) reproduces the hang on net481 x86
(~3% failure rate).
2. **Isolating to raw FSW** — a minimal test using only
`FileSystemWatcher` (no `PendingCreationWatcher`) also fails under the
same conditions on .NET Framework.
3. **Confirming .NET is unaffected** — 504 runs on net11.0 x86 with 8
concurrent threads produced 0 failures.
All 7 CI failures in the issue report are on the same
`net481-windows-Release-x86-NET481_Release-Windows.10.Amd64.Client.Open`
configuration.
## Changes
- **Skip on .NET Framework**: Changed `[Fact]` to
`[ConditionalFact(typeof(PlatformDetection),
nameof(PlatformDetection.IsNotNetFramework))]` since the test depends on
FSW reliability that .NET Framework cannot guarantee under load.
- **Improved timeout**: Replaced `CancellationTokenSource`-based timeout
with `Task.WaitAsync(TimeSpan)` (via `TaskTimeoutExtensions` polyfill)
and added `TaskCreationOptions.RunContinuationsAsynchronously` —
matching the pattern used by all similar tests in
`PhysicalFilesWatcherTests`.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 10, 2026
@svicksvick added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 26, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-FileSystembreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

4 participants

@svick@rosebyte@mrek-msft