Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flaky FSW test #103873

Merged
merged 3 commits into from
Jun 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace System.IO.Tests
Expand Down Expand Up @@ -32,26 +34,32 @@ public class InternalBufferSizeTests : FileSystemWatcherTest
// it's internal buffer (up to some limit). Our docs say that limit is 64KB but testing on Win8.1
// indicates that it is much higher than this: I could grow the buffer up to 128 MB and still see
// that it had an effect. The size needed per operation is determined by the struct layout of
// FILE_NOTIFY_INFORMATION. This works out to 16 + 2 * (Path.GetFileName(file.Path).Length + 1) bytes, where filePath
// FILE_NOTIFY_INFORMATION. This works out to 12 + 2 * (Path.GetFileName(file.Path).Length + 1) bytes, where filePath
// is the path to changed file relative to the path passed into ReadDirectoryChanges.

// At some point we might decide to improve how FSW handles this at which point we'll need
// a better test for Error (perhaps just a mock), but for now there is some value in forcing this limit.
private const int ExcessEventsMultiplier = 200; // 100 does not reliably trigger the error

[Theory]
[InlineData(true)]
[InlineData(false)]
[PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes
[OuterLoop("A little slow")]
public void FileSystemWatcher_InternalBufferSize(bool setToHigherCapacity)
{
ManualResetEvent unblockHandler = new ManualResetEvent(false);
string file = CreateTestFile(TestDirectory, "file");
using (FileSystemWatcher watcher = CreateWatcher(TestDirectory, file, unblockHandler))
{
int internalBufferOperationCapacity = CalculateInternalBufferOperationCapacity(watcher.InternalBufferSize, file);
watcher.InternalBufferSize = 4096; // Minimum
int internalBufferOperationCapacity = watcher.InternalBufferSize / (12 + 2 * (Path.GetFileName(file).Length + 1));

// Set the capacity high to ensure no error events arise.
if (setToHigherCapacity)
watcher.InternalBufferSize = watcher.InternalBufferSize * 12;
{
// Set the capacity high to ensure no error events arise.
watcher.InternalBufferSize = watcher.InternalBufferSize * ExcessEventsMultiplier * 2;
}

Action action = GetAction(unblockHandler, internalBufferOperationCapacity, file);
Action cleanup = GetCleanup(unblockHandler);
Expand All @@ -65,6 +73,7 @@ public void FileSystemWatcher_InternalBufferSize(bool setToHigherCapacity)

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
[OuterLoop("A little slow")]
public void FileSystemWatcher_InternalBufferSize_SynchronizingObject()
{
ManualResetEvent unblockHandler = new ManualResetEvent(false);
Expand All @@ -74,7 +83,8 @@ public void FileSystemWatcher_InternalBufferSize_SynchronizingObject()
TestISynchronizeInvoke invoker = new TestISynchronizeInvoke();
watcher.SynchronizingObject = invoker;

int internalBufferOperationCapacity = CalculateInternalBufferOperationCapacity(watcher.InternalBufferSize, file);
watcher.InternalBufferSize = 4096; // Minimum
int internalBufferOperationCapacity = watcher.InternalBufferSize / (12 + 2 * (Path.GetFileName(file).Length + 1));

Action action = GetAction(unblockHandler, internalBufferOperationCapacity, file);
Action cleanup = GetCleanup(unblockHandler);
Expand All @@ -96,23 +106,30 @@ private FileSystemWatcher CreateWatcher(string testDirectoryPath, string filePat
return watcher;
}

private int CalculateInternalBufferOperationCapacity(int internalBufferSize, string filePath) =>
internalBufferSize / (17 + Path.GetFileName(filePath).Length);

private Action GetAction(ManualResetEvent unblockHandler, int internalBufferOperationCapacity, string filePath)
{
return () =>
{
// generate enough file change events to overflow the default buffer
for (int i = 1; i < internalBufferOperationCapacity * 10; i++)
List<Task> tasks = new();

// Generate enough file change events to overflow the default buffer
// For speed, do this on multiple threads
for (int i = 1; i < internalBufferOperationCapacity * ExcessEventsMultiplier; i++)
{
File.SetLastWriteTime(filePath, DateTime.Now + TimeSpan.FromSeconds(i));
tasks.Add(Task.Run(() =>
{
// Each sets to a different time to ensure it triggers an event
File.SetLastWriteTime(filePath, DateTime.Now + TimeSpan.FromSeconds(i));
}));
}

Task.WaitAll(tasks);

unblockHandler.Set();
};
}


private Action GetCleanup(ManualResetEvent unblockHandler) => () => unblockHandler.Reset();

#endregion
Expand Down
Loading