Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,20 @@ private void TryEnableFileSystemWatcher()
}
}
}
else if (!Directory.Exists(_root))
{
// The watcher still reports EnableRaisingEvents == true, but _root has been
// deleted out from under it. When the watched directory is deleted, the OS
// watch is torn down (on Linux the inotify watch is bound to the deleted
// directory's inode, so recreating the directory will not resurrect it), yet
// EnableRaisingEvents is only reset once OnError runs TryDisableFileSystemWatcher.
// If a token is (re)registered before that happens, we would otherwise leave a
// dead watcher in place and never observe the root being recreated. Tear down
// the stale watch and fall back to watching for the root to reappear.
_fileWatcher.EnableRaisingEvents = false;
needsRootWatcher = true;
_rootWasUnavailable = true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,37 @@ public async Task CreateFileChangeToken_RootDeletedAndRecreated_TokenFiresWhenFi
await changed;
}

[Fact]
[SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")]
public void CreateFileChangeToken_ReRegisterWhileRootMissing_TearsDownStaleWatcher()
{
using var root = new TempDirectory(GetTestFilePath());
string rootPath = root.Path;

using var fileSystemWatcher = new MockFileSystemWatcher(rootPath);

// Call BeginInit, which suspends the watcher so enabling it stores EnableRaisingEvents without starting a real
// OS watch. This lets us delete the root directory below without the watcher's background
// thread asynchronously raising Error (which would make this test racy) while still
// reproducing the state this test targets: EnableRaisingEvents == true over a dead watch.
fileSystemWatcher.BeginInit();

using var physicalFilesWatcher = new PhysicalFilesWatcher(rootPath, fileSystemWatcher, pollForChanges: false);

physicalFilesWatcher.CreateFileChangeToken("file.txt");
Assert.True(fileSystemWatcher.EnableRaisingEvents);

// The watched root is deleted out from under the watcher. On Linux the inotify watch is
// torn down (bound to the now-deleted inode), but EnableRaisingEvents keeps reporting true
// until OnError runs. A token can be re-registered in that window.
Directory.Delete(rootPath);

Comment thread
svick marked this conversation as resolved.
// Re-registering while the root is missing must tear down the stale watcher and fall back
// to watching for the root to reappear, rather than leaving the dead watch in place.
physicalFilesWatcher.CreateFileChangeToken("file.txt");
Assert.False(fileSystemWatcher.EnableRaisingEvents);
}

[Theory]
[MemberData(nameof(WatcherModeData))]
public async Task WildcardToken_DoesNotThrow_WhenRootIsMissing(bool useActivePolling)
Expand Down
Loading