From fe59bc6c59ffa9bc915e68bfb758047a526eab3c Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 26 Aug 2020 22:06:11 -0700 Subject: [PATCH 01/10] Test out INotify exhaustion --- .../tests/PhysicalFileProviderTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 0d9fb9577845c2..8e71ad44808bba 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -87,6 +88,54 @@ public void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes_U GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes(path); } + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public async void PollingFileProviderShouldntConsumeINotifyInstances() + { + List disposables = new List(); + string dir = Path.Combine(Directory.GetCurrentDirectory(), nameof(PollingFileProviderShouldntConsumeINotifyInstances)); + string file = Path.Combine(dir, "test.txt"); + + int callbacks = 0; + Directory.CreateDirectory(dir); + + int maxInstances = int.Parse(File.ReadAllText("/proc/sys/fs/inotify/max_user_instances")); + + try + { + // right now this will fail since we still use FSW + for (int i = 0; i < maxInstances + 2; i++) + { + PhysicalFileProvider pfp = new PhysicalFileProvider(dir) + { + UsePollingFileWatcher = true, + UseActivePolling = true + }; + disposables.Add(pfp); + disposables.Add(pfp.Watch("*").RegisterChangeCallback( + o => callbacks++, + i)); + } + + // trigger an event + File.WriteAllText(file, ""); + + // let some events fire + await Task.Delay(WaitTimeForTokenToFire); + + Assert.NotEqual(0, callbacks); + } + finally + { + foreach (var disposable in disposables) + { + disposable.Dispose(); + Directory.Delete(dir, true); + } + } + } + + private void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes(string path) { using (var provider = new PhysicalFileProvider(Path.GetTempPath())) From beb62faaaa0de93493c465a9d1ada2a81d42fcc0 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 26 Aug 2020 23:20:09 -0700 Subject: [PATCH 02/10] Use DisposableFileSystem and update wait time --- .../tests/PhysicalFileProviderTests.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 8e71ad44808bba..1108746cc66a2d 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders.Internal; using Microsoft.Extensions.FileProviders.Physical; @@ -93,35 +94,32 @@ public void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes_U public async void PollingFileProviderShouldntConsumeINotifyInstances() { List disposables = new List(); - string dir = Path.Combine(Directory.GetCurrentDirectory(), nameof(PollingFileProviderShouldntConsumeINotifyInstances)); - string file = Path.Combine(dir, "test.txt"); + using var root = new DisposableFileSystem(); int callbacks = 0; - Directory.CreateDirectory(dir); - int maxInstances = int.Parse(File.ReadAllText("/proc/sys/fs/inotify/max_user_instances")); - + int instances = maxInstances + 16; try { // right now this will fail since we still use FSW - for (int i = 0; i < maxInstances + 2; i++) + for (int i = 0; i < instances; i++) { - PhysicalFileProvider pfp = new PhysicalFileProvider(dir) + PhysicalFileProvider pfp = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true }; disposables.Add(pfp); disposables.Add(pfp.Watch("*").RegisterChangeCallback( - o => callbacks++, + o => Interlocked.Increment(ref callbacks), i)); } // trigger an event - File.WriteAllText(file, ""); + root.CreateFile("test.txt"); // let some events fire - await Task.Delay(WaitTimeForTokenToFire); + await Task.Delay(WaitTimeForTokenToFire * 16); Assert.NotEqual(0, callbacks); } @@ -130,7 +128,6 @@ public async void PollingFileProviderShouldntConsumeINotifyInstances() foreach (var disposable in disposables) { disposable.Dispose(); - Directory.Delete(dir, true); } } } From e8669aab0e5a1dc2e960077326c757e060e26de3 Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Thu, 27 Aug 2020 12:14:24 -0700 Subject: [PATCH 03/10] Fixing inotify instance limit exhaustion --- .../src/PhysicalFilesWatcher.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs index 94aad3653aacd7..43a573f80461cc 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs @@ -130,7 +130,10 @@ public IChangeToken CreateFileChangeToken(string filter) } IChangeToken changeToken = GetOrAddChangeToken(filter); - TryEnableFileSystemWatcher(); + if (!PollForChanges) + { + TryEnableFileSystemWatcher(); + } return changeToken; } From 9352db20dccf19bb0540386cef5fff6356471c88 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Thu, 27 Aug 2020 16:28:00 -0700 Subject: [PATCH 04/10] Respond to feedback --- .../tests/PhysicalFileProviderTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 1108746cc66a2d..68c2644134b440 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -90,7 +90,7 @@ public void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes_U } [Fact] - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.Linux)] public async void PollingFileProviderShouldntConsumeINotifyInstances() { List disposables = new List(); @@ -101,7 +101,6 @@ public async void PollingFileProviderShouldntConsumeINotifyInstances() int instances = maxInstances + 16; try { - // right now this will fail since we still use FSW for (int i = 0; i < instances; i++) { PhysicalFileProvider pfp = new PhysicalFileProvider(root.RootPath) @@ -131,7 +130,6 @@ public async void PollingFileProviderShouldntConsumeINotifyInstances() } } } - private void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes(string path) { From 589bad44f191efe4edd8dd5e575ac452645839c8 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Fri, 4 Sep 2020 09:33:32 -0700 Subject: [PATCH 05/10] Make PhysicalFileProvider decide if FSW will be used This is less breaking that ignoring the FileSystemWatcher when polling is set as it still lets the PhysicalFileWatcher work in FSW + Polling mode. Now when a FileSystemWatcher is not passed to the PhysicalFileWatcher we'll permit construction (if polling is enabled) and have it behave as exclusively polling. The PhysicalFileProvider will use this mode when both UsePollingFileWatcher and UseActivePolling are set which is what happens when the DOTNET_USE_POLLING_FILE_WATCHER environment variable is set. --- .../src/PhysicalFileProvider.cs | 5 +- .../src/PhysicalFilesWatcher.cs | 58 +++++---- .../src/Resources/Strings.resx | 123 ++++++++++++++++++ .../tests/PhysicalFileProviderTests.cs | 20 +-- 4 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs index e2f3d74151b39c..9c8fafcbf1908d 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFileProvider.cs @@ -158,7 +158,10 @@ internal PhysicalFilesWatcher FileWatcher internal PhysicalFilesWatcher CreateFileWatcher() { string root = PathUtils.EnsureTrailingSlash(Path.GetFullPath(Root)); - return new PhysicalFilesWatcher(root, new FileSystemWatcher(root), UsePollingFileWatcher, _filters) + + // When both UsePollingFileWatcher & UseActivePolling are set, we won't use a FileSystemWatcher. + FileSystemWatcher watcher = UsePollingFileWatcher && UseActivePolling ? null : new FileSystemWatcher(root); + return new PhysicalFilesWatcher(root, watcher, UsePollingFileWatcher, _filters) { UseActivePolling = UseActivePolling, }; diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs index 43a573f80461cc..87f4e31c3d597a 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs @@ -79,17 +79,26 @@ public PhysicalFilesWatcher( ExclusionFilters filters) { _root = root; - _fileWatcher = fileSystemWatcher; - _fileWatcher.IncludeSubdirectories = true; - _fileWatcher.Created += OnChanged; - _fileWatcher.Changed += OnChanged; - _fileWatcher.Renamed += OnRenamed; - _fileWatcher.Deleted += OnChanged; - _fileWatcher.Error += OnError; + + if (fileSystemWatcher != null) + { + _fileWatcher = fileSystemWatcher; + _fileWatcher.IncludeSubdirectories = true; + _fileWatcher.Created += OnChanged; + _fileWatcher.Changed += OnChanged; + _fileWatcher.Renamed += OnRenamed; + _fileWatcher.Deleted += OnChanged; + _fileWatcher.Error += OnError; + } PollForChanges = pollForChanges; _filters = filters; + if (fileSystemWatcher == null && !pollForChanges) + { + throw new ArgumentNullException(nameof(fileSystemWatcher), SR.Error_FileSystemWatcherRequiredWithoutPolling); + } + PollingChangeTokens = new ConcurrentDictionary(); _timerFactory = () => NonCapturingTimer.Create(RaiseChangeEvents, state: PollingChangeTokens, dueTime: TimeSpan.Zero, period: DefaultPollingInterval); } @@ -130,10 +139,7 @@ public IChangeToken CreateFileChangeToken(string filter) } IChangeToken changeToken = GetOrAddChangeToken(filter); - if (!PollForChanges) - { - TryEnableFileSystemWatcher(); - } + TryEnableFileSystemWatcher(); return changeToken; } @@ -242,7 +248,7 @@ internal IChangeToken GetOrAddWildcardChangeToken(string pattern) /// true is invoked from . protected virtual void Dispose(bool disposing) { - _fileWatcher.Dispose(); + _fileWatcher?.Dispose(); _timer?.Dispose(); } @@ -357,27 +363,33 @@ private void ReportChangeForMatchedEntries(string path) private void TryDisableFileSystemWatcher() { - lock (_fileWatcherLock) + if (_fileWatcher != null) { - if (_filePathTokenLookup.IsEmpty && - _wildcardTokenLookup.IsEmpty && - _fileWatcher.EnableRaisingEvents) + lock (_fileWatcherLock) { - // Perf: Turn off the file monitoring if no files to monitor. - _fileWatcher.EnableRaisingEvents = false; + if (_filePathTokenLookup.IsEmpty && + _wildcardTokenLookup.IsEmpty && + _fileWatcher.EnableRaisingEvents) + { + // Perf: Turn off the file monitoring if no files to monitor. + _fileWatcher.EnableRaisingEvents = false; + } } } } private void TryEnableFileSystemWatcher() { - lock (_fileWatcherLock) + if (_fileWatcher != null) { - if ((!_filePathTokenLookup.IsEmpty || !_wildcardTokenLookup.IsEmpty) && - !_fileWatcher.EnableRaisingEvents) + lock (_fileWatcherLock) { - // Perf: Turn off the file monitoring if no files to monitor. - _fileWatcher.EnableRaisingEvents = true; + if ((!_filePathTokenLookup.IsEmpty || !_wildcardTokenLookup.IsEmpty) && + !_fileWatcher.EnableRaisingEvents) + { + // Perf: Turn off the file monitoring if no files to monitor. + _fileWatcher.EnableRaisingEvents = true; + } } } } diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx new file mode 100644 index 00000000000000..061407cc73ac61 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The fileSystemWatcher parameter must be non-null when pollForChanges is false. + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 68c2644134b440..7db25d890d8a9b 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -91,14 +91,20 @@ public void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes_U [Fact] [PlatformSpecific(TestPlatforms.Linux)] - public async void PollingFileProviderShouldntConsumeINotifyInstances() + public void PollingFileProviderShouldntConsumeINotifyInstances() { List disposables = new List(); using var root = new DisposableFileSystem(); - int callbacks = 0; - int maxInstances = int.Parse(File.ReadAllText("/proc/sys/fs/inotify/max_user_instances")); + string maxInstancesFile = "/proc/sys/fs/inotify/max_user_instances"; + Assert.True(File.Exists(maxInstancesFile)); + int maxInstances = int.Parse(File.ReadAllText(maxInstancesFile)); + + // choose an arbitrary number that exceeds max int instances = maxInstances + 16; + + + AutoResetEvent are = new AutoResetEvent(false); try { for (int i = 0; i < instances; i++) @@ -110,17 +116,15 @@ public async void PollingFileProviderShouldntConsumeINotifyInstances() }; disposables.Add(pfp); disposables.Add(pfp.Watch("*").RegisterChangeCallback( - o => Interlocked.Increment(ref callbacks), + o => are.Set(), i)); } // trigger an event root.CreateFile("test.txt"); - // let some events fire - await Task.Delay(WaitTimeForTokenToFire * 16); - - Assert.NotEqual(0, callbacks); + // wait for at least one event. + Assert.True(are.WaitOne(WaitTimeForTokenToFire)); } finally { From f4af1fb033454803c40eba2b3cfe32cbeb092b6c Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Mon, 14 Sep 2020 09:03:47 -0700 Subject: [PATCH 06/10] Address PR feedback --- .../src/PhysicalFilesWatcher.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs index 87f4e31c3d597a..cf018b8244543c 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PhysicalFilesWatcher.cs @@ -78,6 +78,11 @@ public PhysicalFilesWatcher( bool pollForChanges, ExclusionFilters filters) { + if (fileSystemWatcher == null && !pollForChanges) + { + throw new ArgumentNullException(nameof(fileSystemWatcher), SR.Error_FileSystemWatcherRequiredWithoutPolling); + } + _root = root; if (fileSystemWatcher != null) @@ -94,11 +99,6 @@ public PhysicalFilesWatcher( PollForChanges = pollForChanges; _filters = filters; - if (fileSystemWatcher == null && !pollForChanges) - { - throw new ArgumentNullException(nameof(fileSystemWatcher), SR.Error_FileSystemWatcherRequiredWithoutPolling); - } - PollingChangeTokens = new ConcurrentDictionary(); _timerFactory = () => NonCapturingTimer.Create(RaiseChangeEvents, state: PollingChangeTokens, dueTime: TimeSpan.Zero, period: DefaultPollingInterval); } From 6b177fdbaa585034dde439588663c5283f24d0f1 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Mon, 14 Sep 2020 12:42:19 -0700 Subject: [PATCH 07/10] Tweak test threshold --- .../tests/PhysicalFileProviderTests.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 7db25d890d8a9b..56f21422360744 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Extensions.FileProviders public class PhysicalFileProviderTests { private const int WaitTimeForTokenToFire = 500; + private const int WaitTimeForTokenCallback = 10000; [Fact] public void GetFileInfoReturnsNotFoundFileInfoForNullPath() @@ -103,7 +104,6 @@ public void PollingFileProviderShouldntConsumeINotifyInstances() // choose an arbitrary number that exceeds max int instances = maxInstances + 16; - AutoResetEvent are = new AutoResetEvent(false); try { @@ -115,16 +115,14 @@ public void PollingFileProviderShouldntConsumeINotifyInstances() UseActivePolling = true }; disposables.Add(pfp); - disposables.Add(pfp.Watch("*").RegisterChangeCallback( - o => are.Set(), - i)); + disposables.Add(pfp.Watch("*").RegisterChangeCallback(_ => are.Set(), null)); } // trigger an event root.CreateFile("test.txt"); // wait for at least one event. - Assert.True(are.WaitOne(WaitTimeForTokenToFire)); + Assert.True(are.WaitOne(WaitTimeForTokenCallback)); } finally { From c191d3d9f5146ffeda7f3f0022455dea0e5bf071 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Mon, 9 Nov 2020 14:44:04 -0800 Subject: [PATCH 08/10] Add test around deleting watched directory. --- .../tests/PhysicalFileProviderTests.cs | 83 +++++++++++++------ 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 56f21422360744..8791251575e39e 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -95,40 +95,45 @@ public void GetFileInfoReturnsNotFoundFileInfoForIllegalPathWithLeadingSlashes_U public void PollingFileProviderShouldntConsumeINotifyInstances() { List disposables = new List(); - using var root = new DisposableFileSystem(); + using (var root = new DisposableFileSystem()) + { + string maxInstancesFile = "/proc/sys/fs/inotify/max_user_instances"; + Assert.True(File.Exists(maxInstancesFile)); + int maxInstances = int.Parse(File.ReadAllText(maxInstancesFile)); - string maxInstancesFile = "/proc/sys/fs/inotify/max_user_instances"; - Assert.True(File.Exists(maxInstancesFile)); - int maxInstances = int.Parse(File.ReadAllText(maxInstancesFile)); + // choose an arbitrary number that exceeds max + int instances = maxInstances + 16; - // choose an arbitrary number that exceeds max - int instances = maxInstances + 16; + AutoResetEvent are = new AutoResetEvent(false); - AutoResetEvent are = new AutoResetEvent(false); - try - { - for (int i = 0; i < instances; i++) + var oldPollingInterval = PhysicalFilesWatcher.DefaultPollingInterval; + try { - PhysicalFileProvider pfp = new PhysicalFileProvider(root.RootPath) + PhysicalFilesWatcher.DefaultPollingInterval = TimeSpan.FromMilliseconds(WaitTimeForTokenToFire); + for (int i = 0; i < instances; i++) { - UsePollingFileWatcher = true, - UseActivePolling = true - }; - disposables.Add(pfp); - disposables.Add(pfp.Watch("*").RegisterChangeCallback(_ => are.Set(), null)); - } + PhysicalFileProvider pfp = new PhysicalFileProvider(root.RootPath) + { + UsePollingFileWatcher = true, + UseActivePolling = true + }; + disposables.Add(pfp); + disposables.Add(pfp.Watch("*").RegisterChangeCallback(_ => are.Set(), null)); + } - // trigger an event - root.CreateFile("test.txt"); + // trigger an event + root.CreateFile("test.txt"); - // wait for at least one event. - Assert.True(are.WaitOne(WaitTimeForTokenCallback)); - } - finally - { - foreach (var disposable in disposables) + // wait for at least one event. + Assert.True(are.WaitOne(WaitTimeForTokenCallback)); + } + finally { - disposable.Dispose(); + PhysicalFilesWatcher.DefaultPollingInterval = oldPollingInterval; + foreach (var disposable in disposables) + { + disposable.Dispose(); + } } } } @@ -1525,5 +1530,31 @@ public void CreateFileWatcher_CreatesWatcherWithPollingAndActiveFlags() Assert.True(fileWatcher.UseActivePolling); } } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task CanDeleteWatchedDirectory(bool usePolling, bool useActivePolling) + { + using (var root = new DisposableFileSystem()) + using (var provider = new PhysicalFileProvider(root.RootPath)) + { + var fileName = Path.GetRandomFileName(); + var fileLocation = Path.Combine(root.RootPath, fileName); + PollingFileChangeToken.PollingInterval = TimeSpan.FromMilliseconds(10); + + provider.UsePollingFileWatcher = usePolling; + provider.UseActivePolling = useActivePolling; + + root.CreateFile(fileName); + var token = provider.Watch(fileName); + Directory.Delete(root.RootPath, true); + + await Task.Delay(WaitTimeForTokenToFire); + + Assert.True(token.HasChanged); + } + } } } From ec30e447d54e6e07af98d22d1c7339d25817a3db Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Tue, 10 Nov 2020 12:01:58 -0800 Subject: [PATCH 09/10] Make directory delete test use polling We have to avoid exclusively using FileSystemWatcher since it looks like FSW misses file deletes that happen as a result of directory delete on OSX. --- .../tests/PhysicalFileProviderTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 8791251575e39e..dc4e48b52c5e4c 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -1532,10 +1532,10 @@ public void CreateFileWatcher_CreatesWatcherWithPollingAndActiveFlags() } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task CanDeleteWatchedDirectory(bool usePolling, bool useActivePolling) + [InlineData(false)] + [InlineData(true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/34580", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] + public async Task CanDeleteWatchedDirectory(bool useActivePolling) { using (var root = new DisposableFileSystem()) using (var provider = new PhysicalFileProvider(root.RootPath)) @@ -1544,14 +1544,14 @@ public async Task CanDeleteWatchedDirectory(bool usePolling, bool useActivePolli var fileLocation = Path.Combine(root.RootPath, fileName); PollingFileChangeToken.PollingInterval = TimeSpan.FromMilliseconds(10); - provider.UsePollingFileWatcher = usePolling; + provider.UsePollingFileWatcher = true; // We must use polling due to https://github.com/dotnet/runtime/issues/44484 provider.UseActivePolling = useActivePolling; root.CreateFile(fileName); var token = provider.Watch(fileName); Directory.Delete(root.RootPath, true); - await Task.Delay(WaitTimeForTokenToFire); + await Task.Delay(WaitTimeForTokenToFire).ConfigureAwait(false); Assert.True(token.HasChanged); } From 7b79832364d2bd8953ee8c0f35c301f80b3cc614 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 11 Nov 2020 16:46:18 -0800 Subject: [PATCH 10/10] Remove dead code from test. --- .../tests/PhysicalFileProviderTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index dc4e48b52c5e4c..97b633808f4533 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -1541,7 +1541,6 @@ public async Task CanDeleteWatchedDirectory(bool useActivePolling) using (var provider = new PhysicalFileProvider(root.RootPath)) { var fileName = Path.GetRandomFileName(); - var fileLocation = Path.Combine(root.RootPath, fileName); PollingFileChangeToken.PollingInterval = TimeSpan.FromMilliseconds(10); provider.UsePollingFileWatcher = true; // We must use polling due to https://github.com/dotnet/runtime/issues/44484