Fix StackOverflowException caused when creating the watcher on directories from a network share
This commit is contained in:
parent
4cfeca184c
commit
d058fe5a39
|
|
@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue