Skip to content

Commit

Permalink
Handle FileNotFound for symlinks when using polling (#56915)
Browse files Browse the repository at this point in the history
* Handle FileNotFound for symlinks when using polling

* Re-enable tests

* Use CTS instead of Task.Wait(TimeSpan) to fix issues in browser.
  • Loading branch information
jozkee authored Aug 10, 2021
1 parent 4566436 commit a57a9ec
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,28 @@ public static bool IsExcluded(FileSystemInfo fileSystemInfo, ExclusionFilters fi
// If file is a link, and link target does not exists, return DateTime.MinValue
// since the link's LastWriteTimeUtc doesn't convey anything for this scenario.
// If file is not a link, return null to inform the caller that file is not a link.
public static DateTime? GetFileLinkTargetLastWriteTimeUtc(FileInfo fileInfo)
public static DateTime? GetFileLinkTargetLastWriteTimeUtc(FileInfo fileInfo, bool isSecondTry = false)
{
#if NETCOREAPP
Debug.Assert(fileInfo.Exists);
if (fileInfo.LinkTarget != null)
{
FileSystemInfo targetInfo = fileInfo.ResolveLinkTarget(returnFinalTarget: true);
if (targetInfo.Exists)
try
{
return targetInfo.LastWriteTimeUtc;
FileSystemInfo targetInfo = fileInfo.ResolveLinkTarget(returnFinalTarget: true);
if (targetInfo.Exists)
{
return targetInfo.LastWriteTimeUtc;
}
}
catch (FileNotFoundException)
{
// The file ceased to exist between LinkTarget and ResolveLinkTarget.
// Try one more time, if it fails again just give up.
if (!isSecondTry)
{
GetFileLinkTargetLastWriteTimeUtc(fileInfo, isSecondTry: true);
}
}

return DateTime.MinValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1540,7 +1540,6 @@ public void UsePollingFileWatcher_FileWatcherNotNull_ReturnsFalse()
[Theory]
[InlineData(false)]
[InlineData(true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged(bool useWildcard)
{
// Arrange
Expand All @@ -1550,25 +1549,27 @@ public async Task UsePollingFileWatcher_UseActivePolling_HasChanged(bool useWild
File.WriteAllText(filePath, "v1.1");

using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : fileName);
IChangeToken changeToken = provider.Watch(useWildcard ? "*" : fileName);

var tcs = new TaskCompletionSource<object>();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(null); }, null);
var tcs = new TaskCompletionSource<bool>();
changeToken.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

// Act
await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.
File.WriteAllText(filePath, "v1.2");

// Assert
Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
Assert.True(await tcs.Task,
$"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}");
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_FileDeleted(bool useWildcard)
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_FileDeleted(bool useWildcard)
{
// Arrange
using var root = new DisposableFileSystem();
Expand All @@ -1578,16 +1579,19 @@ public void UsePollingFileWatcher_UseActivePolling_HasChanged_FileDeleted(bool u

string filter = useWildcard ? "*" : fileName;
using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(filter);
IChangeToken changeToken = provider.Watch(filter);

var tcs = new TaskCompletionSource<bool>();
changeToken.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

var tcs = new TaskCompletionSource<object>();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(null); }, null);
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

// Act
File.Delete(filePath);

// Assert
Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
Assert.True(await tcs.Task,
$"Change event was not raised - current time: {DateTime.UtcNow:O}, file Exists: {File.Exists(filePath)}.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Xunit;
Expand All @@ -14,7 +15,6 @@ public partial class PhysicalFileProviderTests
[Theory]
[InlineData(false)]
[InlineData(true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink(bool useWildcard)
{
// Arrange
Expand All @@ -30,23 +30,25 @@ public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink
using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);

var tcs = new TaskCompletionSource();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
var tcs = new TaskCompletionSource<bool>();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

// Act
await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.
File.WriteAllText(filePath, "v1.2");

// Assert
Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
Assert.True(await tcs.Task,
$"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetNotExists(bool useWildcard)
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetNotExists(bool useWildcard)
{
// Arrange
using var rootOfLink = new DisposableFileSystem();
Expand All @@ -59,19 +61,19 @@ public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_Targe
IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);

var tcs = new TaskCompletionSource();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
token.RegisterChangeCallback(_ => { Assert.True(false, "Change event was raised when it was not expected."); }, null);

// Assert
Assert.False(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
"Change event was raised when it was not expected.");
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

await Assert.ThrowsAsync<TaskCanceledException>(() => tcs.Task);
}

[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetChanged(bool useWildcard, bool linkWasBroken)
{
// Arrange
Expand All @@ -96,23 +98,25 @@ public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink
using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(filter);

var tcs = new TaskCompletionSource();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
var tcs = new TaskCompletionSource<bool>();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

// Act - Change link target to file 2.
File.Delete(linkPath);
File.CreateSymbolicLink(linkPath, file2Path);

// Assert - It should report the change regardless of the timestamp being older.
Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
Assert.True(await tcs.Task,
$"Change event was not raised - current time: {DateTime.UtcNow:O}, file1 LastWriteTimeUtc: {File.GetLastWriteTimeUtc(file1Path):O}, file2 LastWriteTime: {File.GetLastWriteTimeUtc(file2Path):O}.");
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/56190", TestPlatforms.AnyUnix)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetDeleted(bool useWildcard)
public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetDeleted(bool useWildcard)
{
// Arrange
using var rootOfFile = new DisposableFileSystem();
Expand All @@ -129,14 +133,17 @@ public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_Targe
using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(filter);

var tcs = new TaskCompletionSource();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
var tcs = new TaskCompletionSource<bool>();
token.RegisterChangeCallback(_ => { tcs.TrySetResult(true); }, null);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cts.Token.Register(() => tcs.TrySetCanceled());

// Act
File.Delete(linkPath);

// Assert
Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
Assert.True(await tcs.Task,
$"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
}
}
Expand Down

0 comments on commit a57a9ec

Please sign in to comment.