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.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.DotNet.Watcher.Tools;
|
using Microsoft.DotNet.Watcher.Tools;
|
||||||
|
|
@ -19,6 +20,17 @@ namespace Microsoft.DotNet.Watcher
|
||||||
public IList<string> RemainingArguments { get; private set; }
|
public IList<string> RemainingArguments { get; private set; }
|
||||||
public bool ListFiles { 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)
|
public static CommandLineOptions Parse(string[] args, IConsole console)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(args, nameof(args));
|
Ensure.NotNull(args, nameof(args));
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.DotNet.Watcher
|
||||||
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
|
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
currentRunCancellationSource.Token))
|
currentRunCancellationSource.Token))
|
||||||
using (var fileSetWatcher = new FileSetWatcher(fileSet))
|
using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter))
|
||||||
{
|
{
|
||||||
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
||||||
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,15 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
public class FileSetWatcher : IDisposable
|
public class FileSetWatcher : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FileWatcher _fileWatcher = new FileWatcher();
|
private readonly FileWatcher _fileWatcher;
|
||||||
private readonly IFileSet _fileSet;
|
private readonly IFileSet _fileSet;
|
||||||
|
|
||||||
public FileSetWatcher(IFileSet fileSet)
|
public FileSetWatcher(IFileSet fileSet, IReporter reporter)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(fileSet, nameof(fileSet));
|
Ensure.NotNull(fileSet, nameof(fileSet));
|
||||||
|
|
||||||
_fileSet = fileSet;
|
_fileSet = fileSet;
|
||||||
|
_fileWatcher = new FileWatcher(reporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetChangedFileAsync(CancellationToken cancellationToken)
|
public async Task<string> GetChangedFileAsync(CancellationToken cancellationToken)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -12,7 +13,18 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
private bool _disposed;
|
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;
|
public event Action<string> OnFileChange;
|
||||||
|
|
||||||
|
|
@ -33,8 +45,11 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
foreach (var watcher in _watchers)
|
foreach (var watcher in _watchers)
|
||||||
{
|
{
|
||||||
|
watcher.Value.OnFileChange -= WatcherChangedHandler;
|
||||||
|
watcher.Value.OnError -= WatcherErrorHandler;
|
||||||
watcher.Value.Dispose();
|
watcher.Value.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_watchers.Clear();
|
_watchers.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,11 +81,20 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
var newWatcher = FileWatcherFactory.CreateWatcher(directory);
|
var newWatcher = FileWatcherFactory.CreateWatcher(directory);
|
||||||
newWatcher.OnFileChange += WatcherChangedHandler;
|
newWatcher.OnFileChange += WatcherChangedHandler;
|
||||||
|
newWatcher.OnError += WatcherErrorHandler;
|
||||||
newWatcher.EnableRaisingEvents = true;
|
newWatcher.EnableRaisingEvents = true;
|
||||||
|
|
||||||
_watchers.Add(directory, newWatcher);
|
_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)
|
private void WatcherChangedHandler(object sender, string changedPath)
|
||||||
{
|
{
|
||||||
NotifyChange(changedPath);
|
NotifyChange(changedPath);
|
||||||
|
|
@ -90,7 +114,9 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
_watchers.Remove(directory);
|
_watchers.Remove(directory);
|
||||||
|
|
||||||
watcher.EnableRaisingEvents = false;
|
watcher.EnableRaisingEvents = false;
|
||||||
|
|
||||||
watcher.OnFileChange -= WatcherChangedHandler;
|
watcher.OnFileChange -= WatcherChangedHandler;
|
||||||
|
watcher.OnError -= WatcherErrorHandler;
|
||||||
|
|
||||||
watcher.Dispose();
|
watcher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,6 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
internal class DotnetFileWatcher : IFileSystemWatcher
|
internal class DotnetFileWatcher : IFileSystemWatcher
|
||||||
{
|
{
|
||||||
private readonly Func<string, FileSystemWatcher> _watcherFactory;
|
private readonly Func<string, FileSystemWatcher> _watcherFactory;
|
||||||
private readonly string _watchedDirectory;
|
|
||||||
|
|
||||||
private FileSystemWatcher _fileSystemWatcher;
|
private FileSystemWatcher _fileSystemWatcher;
|
||||||
|
|
||||||
|
|
@ -26,14 +26,16 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
Ensure.NotNull(fileSystemWatcherFactory, nameof(fileSystemWatcherFactory));
|
Ensure.NotNull(fileSystemWatcherFactory, nameof(fileSystemWatcherFactory));
|
||||||
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
|
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
|
||||||
|
|
||||||
_watchedDirectory = watchedDirectory;
|
BasePath = watchedDirectory;
|
||||||
_watcherFactory = fileSystemWatcherFactory;
|
_watcherFactory = fileSystemWatcherFactory;
|
||||||
CreateFileSystemWatcher();
|
CreateFileSystemWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<string> OnFileChange;
|
public event EventHandler<string> OnFileChange;
|
||||||
|
|
||||||
public event EventHandler OnError;
|
public event EventHandler<Exception> OnError;
|
||||||
|
|
||||||
|
public string BasePath { get; }
|
||||||
|
|
||||||
private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory)
|
private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory)
|
||||||
{
|
{
|
||||||
|
|
@ -44,10 +46,18 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
private void WatcherErrorHandler(object sender, ErrorEventArgs e)
|
private void WatcherErrorHandler(object sender, ErrorEventArgs e)
|
||||||
{
|
{
|
||||||
// Recreate the watcher
|
var exception = e.GetException();
|
||||||
CreateFileSystemWatcher();
|
|
||||||
|
|
||||||
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)
|
private void WatcherRenameHandler(object sender, RenamedEventArgs e)
|
||||||
|
|
@ -99,7 +109,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
_fileSystemWatcher.Dispose();
|
_fileSystemWatcher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileSystemWatcher = _watcherFactory(_watchedDirectory);
|
_fileSystemWatcher = _watcherFactory(BasePath);
|
||||||
_fileSystemWatcher.IncludeSubdirectories = true;
|
_fileSystemWatcher.IncludeSubdirectories = true;
|
||||||
|
|
||||||
_fileSystemWatcher.Created += WatcherChangeHandler;
|
_fileSystemWatcher.Created += WatcherChangeHandler;
|
||||||
|
|
@ -114,8 +124,8 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
public bool EnableRaisingEvents
|
public bool EnableRaisingEvents
|
||||||
{
|
{
|
||||||
get { return _fileSystemWatcher.EnableRaisingEvents; }
|
get => _fileSystemWatcher.EnableRaisingEvents;
|
||||||
set { _fileSystemWatcher.EnableRaisingEvents = value; }
|
set => _fileSystemWatcher.EnableRaisingEvents = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
public static class FileWatcherFactory
|
public static class FileWatcherFactory
|
||||||
{
|
{
|
||||||
public static IFileSystemWatcher CreateWatcher(string watchedDirectory)
|
public static IFileSystemWatcher CreateWatcher(string watchedDirectory)
|
||||||
{
|
=> CreateWatcher(watchedDirectory, CommandLineOptions.IsPollingEnabled);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher)
|
public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
event EventHandler<string> OnFileChange;
|
event EventHandler<string> OnFileChange;
|
||||||
|
|
||||||
event EventHandler OnError;
|
event EventHandler<Exception> OnError;
|
||||||
|
|
||||||
|
string BasePath { get; }
|
||||||
|
|
||||||
bool EnableRaisingEvents { get; set; }
|
bool EnableRaisingEvents { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
|
Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory));
|
||||||
|
|
||||||
_watchedDirectory = new DirectoryInfo(watchedDirectory);
|
_watchedDirectory = new DirectoryInfo(watchedDirectory);
|
||||||
|
BasePath = _watchedDirectory.FullName;
|
||||||
|
|
||||||
_pollingThread = new Thread(new ThreadStart(PollingLoop));
|
_pollingThread = new Thread(new ThreadStart(PollingLoop));
|
||||||
_pollingThread.IsBackground = true;
|
_pollingThread.IsBackground = true;
|
||||||
|
|
@ -44,15 +45,14 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
public event EventHandler<string> OnFileChange;
|
public event EventHandler<string> OnFileChange;
|
||||||
|
|
||||||
#pragma warning disable CS0067 // not used
|
#pragma warning disable CS0067 // not used
|
||||||
public event EventHandler OnError;
|
public event EventHandler<Exception> OnError;
|
||||||
#pragma warning restore
|
#pragma warning restore
|
||||||
|
|
||||||
|
public string BasePath { get; }
|
||||||
|
|
||||||
public bool EnableRaisingEvents
|
public bool EnableRaisingEvents
|
||||||
{
|
{
|
||||||
get
|
get => _raiseEvents;
|
||||||
{
|
|
||||||
return _raiseEvents;
|
|
||||||
}
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
EnsureNotDisposed();
|
EnsureNotDisposed();
|
||||||
|
|
@ -125,7 +125,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true);
|
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true);
|
||||||
}
|
}
|
||||||
catch(FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, false);
|
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, false);
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = dirInfo.EnumerateFileSystemInfos("*.*");
|
var entities = dirInfo.EnumerateFileSystemInfos("*.*");
|
||||||
foreach (var entity in entities)
|
foreach (var entity in entities)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
var fileSet = new FileSet(new[] { _projectFile });
|
var fileSet = new FileSet(new[] { _projectFile });
|
||||||
|
|
||||||
using (var watcher = new FileSetWatcher(fileSet))
|
using (var watcher = new FileSetWatcher(fileSet, _reporter))
|
||||||
{
|
{
|
||||||
await watcher.GetChangedFileAsync(cancellationToken);
|
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)
|
await new DotNetWatcher(reporter)
|
||||||
.WatchAsync(processInfo, fileSetFactory, cancellationToken);
|
.WatchAsync(processInfo, fileSetFactory, cancellationToken);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue