Fix StackOverflowException caused when creating the watcher on directories from a network share

This commit is contained in:
Nate McMaster 2017-07-14 11:27:01 -07:00
parent 4cfeca184c
commit d058fe5a39
11 changed files with 104 additions and 31 deletions

25
shared/NullReporter.cs Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Tools.Internal
{
public class NullReporter : IReporter
{
private NullReporter()
{ }
public static IReporter Singleton { get; } = new NullReporter();
public void Verbose(string message)
{ }
public void Output(string message)
{ }
public void Warn(string message)
{ }
public void Error(string message)
{ }
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.DotNet.Watcher.Tools;
@ -19,6 +20,17 @@ namespace Microsoft.DotNet.Watcher
public IList<string> RemainingArguments { get; private set; }
public bool ListFiles { get; private set; }
public static bool IsPollingEnabled
{
get
{
var envVar = Environment.GetEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER");
return envVar != null &&
(envVar.Equals("1", StringComparison.OrdinalIgnoreCase) ||
envVar.Equals("true", StringComparison.OrdinalIgnoreCase));
}
}
public static CommandLineOptions Parse(string[] args, IConsole console)
{
Ensure.NotNull(args, nameof(args));

View File

@ -51,7 +51,7 @@ namespace Microsoft.DotNet.Watcher
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
currentRunCancellationSource.Token))
using (var fileSetWatcher = new FileSetWatcher(fileSet))
using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter))
{
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);

View File

@ -11,14 +11,15 @@ namespace Microsoft.DotNet.Watcher.Internal
{
public class FileSetWatcher : IDisposable
{
private readonly FileWatcher _fileWatcher = new FileWatcher();
private readonly FileWatcher _fileWatcher;
private readonly IFileSet _fileSet;
public FileSetWatcher(IFileSet fileSet)
public FileSetWatcher(IFileSet fileSet, IReporter reporter)
{
Ensure.NotNull(fileSet, nameof(fileSet));
_fileSet = fileSet;
_fileWatcher = new FileWatcher(reporter);
}
public async Task<string> GetChangedFileAsync(CancellationToken cancellationToken)

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Internal
{
@ -12,7 +13,18 @@ namespace Microsoft.DotNet.Watcher.Internal
{
private bool _disposed;
private readonly IDictionary<string, IFileSystemWatcher> _watchers = new Dictionary<string, IFileSystemWatcher>();
private readonly IDictionary<string, IFileSystemWatcher> _watchers;
private readonly IReporter _reporter;
public FileWatcher()
: this(NullReporter.Singleton)
{ }
public FileWatcher(IReporter reporter)
{
_reporter = reporter ?? throw new ArgumentNullException(nameof(reporter));
_watchers = new Dictionary<string, IFileSystemWatcher>();
}
public event Action<string> OnFileChange;
@ -33,8 +45,11 @@ namespace Microsoft.DotNet.Watcher.Internal
foreach (var watcher in _watchers)
{
watcher.Value.OnFileChange -= WatcherChangedHandler;
watcher.Value.OnError -= WatcherErrorHandler;
watcher.Value.Dispose();
}
_watchers.Clear();
}
@ -66,11 +81,20 @@ namespace Microsoft.DotNet.Watcher.Internal
var newWatcher = FileWatcherFactory.CreateWatcher(directory);
newWatcher.OnFileChange += WatcherChangedHandler;
newWatcher.OnError += WatcherErrorHandler;
newWatcher.EnableRaisingEvents = true;
_watchers.Add(directory, newWatcher);
}
private void WatcherErrorHandler(object sender, Exception error)
{
if (sender is IFileSystemWatcher watcher)
{
_reporter.Warn($"The file watcher observing '{watcher.BasePath}' encountered an error: {error.Message}");
}
}
private void WatcherChangedHandler(object sender, string changedPath)
{
NotifyChange(changedPath);
@ -90,7 +114,9 @@ namespace Microsoft.DotNet.Watcher.Internal
_watchers.Remove(directory);
watcher.EnableRaisingEvents = false;
watcher.OnFileChange -= WatcherChangedHandler;
watcher.OnError -= WatcherErrorHandler;
watcher.Dispose();
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.IO;
using Microsoft.Extensions.Tools.Internal;
@ -10,7 +11,6 @@ namespace Microsoft.DotNet.Watcher.Internal
internal class DotnetFileWatcher : IFileSystemWatcher
{
private readonly Func<string, FileSystemWatcher> _watcherFactory;
private readonly string _watchedDirectory;
private FileSystemWatcher _fileSystemWatcher;
@ -26,14 +26,16 @@ namespace Microsoft.DotNet.Watcher.Internal
Ensure.NotNull(fileSystemWatcherFactory, nameof(fileSystemWatcherFactory));
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
_watchedDirectory = watchedDirectory;
BasePath = watchedDirectory;
_watcherFactory = fileSystemWatcherFactory;
CreateFileSystemWatcher();
}
public event EventHandler<string> OnFileChange;
public event EventHandler OnError;
public event EventHandler<Exception> OnError;
public string BasePath { get; }
private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory)
{
@ -44,10 +46,18 @@ namespace Microsoft.DotNet.Watcher.Internal
private void WatcherErrorHandler(object sender, ErrorEventArgs e)
{
// Recreate the watcher
CreateFileSystemWatcher();
var exception = e.GetException();
OnError?.Invoke(this, null);
// Win32Exception may be triggered when setting EnableRaisingEvents on a file system type
// that is not supported, such as a network share. Don't attempt to recreate the watcher
// in this case as it will cause a StackOverflowException
if (!(exception is Win32Exception))
{
// Recreate the watcher if it is a recoverable error.
CreateFileSystemWatcher();
}
OnError?.Invoke(this, exception);
}
private void WatcherRenameHandler(object sender, RenamedEventArgs e)
@ -99,7 +109,7 @@ namespace Microsoft.DotNet.Watcher.Internal
_fileSystemWatcher.Dispose();
}
_fileSystemWatcher = _watcherFactory(_watchedDirectory);
_fileSystemWatcher = _watcherFactory(BasePath);
_fileSystemWatcher.IncludeSubdirectories = true;
_fileSystemWatcher.Created += WatcherChangeHandler;
@ -114,8 +124,8 @@ namespace Microsoft.DotNet.Watcher.Internal
public bool EnableRaisingEvents
{
get { return _fileSystemWatcher.EnableRaisingEvents; }
set { _fileSystemWatcher.EnableRaisingEvents = value; }
get => _fileSystemWatcher.EnableRaisingEvents;
set => _fileSystemWatcher.EnableRaisingEvents = value;
}
public void Dispose()

View File

@ -8,15 +8,7 @@ namespace Microsoft.DotNet.Watcher.Internal
public static class FileWatcherFactory
{
public static IFileSystemWatcher CreateWatcher(string watchedDirectory)
{
var envVar = Environment.GetEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER");
var usePollingWatcher =
envVar != null &&
(envVar.Equals("1", StringComparison.OrdinalIgnoreCase) ||
envVar.Equals("true", StringComparison.OrdinalIgnoreCase));
return CreateWatcher(watchedDirectory, usePollingWatcher);
}
=> CreateWatcher(watchedDirectory, CommandLineOptions.IsPollingEnabled);
public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher)
{

View File

@ -9,7 +9,9 @@ namespace Microsoft.DotNet.Watcher.Internal
{
event EventHandler<string> OnFileChange;
event EventHandler OnError;
event EventHandler<Exception> OnError;
string BasePath { get; }
bool EnableRaisingEvents { get; set; }
}

View File

@ -31,6 +31,7 @@ namespace Microsoft.DotNet.Watcher.Internal
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
_watchedDirectory = new DirectoryInfo(watchedDirectory);
BasePath = _watchedDirectory.FullName;
_pollingThread = new Thread(new ThreadStart(PollingLoop));
_pollingThread.IsBackground = true;
@ -44,15 +45,14 @@ namespace Microsoft.DotNet.Watcher.Internal
public event EventHandler<string> OnFileChange;
#pragma warning disable CS0067 // not used
public event EventHandler OnError;
public event EventHandler<Exception> OnError;
#pragma warning restore
public string BasePath { get; }
public bool EnableRaisingEvents
{
get
{
return _raiseEvents;
}
get => _raiseEvents;
set
{
EnsureNotDisposed();
@ -125,7 +125,7 @@ namespace Microsoft.DotNet.Watcher.Internal
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true);
}
catch(FileNotFoundException)
catch (FileNotFoundException)
{
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, false);
}
@ -187,7 +187,7 @@ namespace Microsoft.DotNet.Watcher.Internal
{
return;
}
var entities = dirInfo.EnumerateFileSystemInfos("*.*");
foreach (var entity in entities)
{

View File

@ -144,7 +144,7 @@ namespace Microsoft.DotNet.Watcher.Internal
var fileSet = new FileSet(new[] { _projectFile });
using (var watcher = new FileSetWatcher(fileSet))
using (var watcher = new FileSetWatcher(fileSet, _reporter))
{
await watcher.GetChangedFileAsync(cancellationToken);

View File

@ -160,6 +160,11 @@ namespace Microsoft.DotNet.Watcher
},
};
if (CommandLineOptions.IsPollingEnabled)
{
_reporter.Output("Polling file watcher is enabled");
}
await new DotNetWatcher(reporter)
.WatchAsync(processInfo, fileSetFactory, cancellationToken);