Skip to content

Commit

Permalink
FileSystemWatcher.Linux: handle races while adding child directories. (
Browse files Browse the repository at this point in the history
…#64906)

* FileSystemWatcher.Linux: handle races while adding child directories.

This handles the child directory getting removed or replaced by a file
while we are adding a watch for it or enumerating its subdirectories.

* Pass other enumeration exceptions to the user.
  • Loading branch information
tmds committed Feb 18, 2022
1 parent 26a1c7e commit 89e5469
Showing 1 changed file with 33 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,8 @@ private void AddDirectoryWatch(WatchedDirectory parent, string directoryName)
// against the handle, so we'd deadlock if we relied on that approach. Instead, we want to follow
// the approach of removing all watches when we're done, which means we also don't want to
// add any new watches once the count hits zero.
if (parent == null || _wdToPathMap.Count > 0)
if (_wdToPathMap.Count > 0)
{
Debug.Assert(parent != null || _wdToPathMap.Count == 0);
AddDirectoryWatchUnlocked(parent, directoryName);
}
}
Expand Down Expand Up @@ -361,6 +360,15 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory? parent, string director
// raise the Error event with the exception and let the user decide how to handle it.

Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();

// Don't report an error when we can't add a watch because the child directory
// was removed or replaced by a file.
if (hasParent && (error.Error == Interop.Error.ENOENT ||
error.Error == Interop.Error.ENOTDIR))
{
return;
}

Exception exc;
if (error.Error == Interop.Error.ENOSPC)
{
Expand Down Expand Up @@ -432,16 +440,30 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory? parent, string director
// asked for subdirectories to be included.
if (isNewDirectory && _includeSubdirectories)
{
// This method is recursive. If we expect to see hierarchies
// so deep that it would cause us to overflow the stack, we could
// consider using an explicit stack object rather than recursion.
// This is unlikely, however, given typical directory names
// and max path limits.
foreach (string subDir in Directory.EnumerateDirectories(fullPath))
try
{
AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
// AddDirectoryWatchUnlocked will add the new directory to
// this.Children, so we don't have to / shouldn't also do it here.
// This method is recursive. If we expect to see hierarchies
// so deep that it would cause us to overflow the stack, we could
// consider using an explicit stack object rather than recursion.
// This is unlikely, however, given typical directory names
// and max path limits.
foreach (string subDir in Directory.EnumerateDirectories(fullPath))
{
AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
// AddDirectoryWatchUnlocked will add the new directory to
// this.Children, so we don't have to / shouldn't also do it here.
}
}
catch (DirectoryNotFoundException)
{ } // The child directory was removed.
catch (IOException ex) when (ex.HResult == Interop.Error.ENOTDIR.Info().RawErrno)
{ } // The child directory was replaced by a file.
catch (Exception ex)
{
if (_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher))
{
watcher.OnError(new ErrorEventArgs(ex));
}
}
}
}
Expand Down

0 comments on commit 89e5469

Please sign in to comment.