Skip to content

MockFileSystem.File.Move raises FileSystemWatcher event with delay #2 #938

@teneko

Description

@teneko

Continuation of #930 (I am not the author)

I do not get a reliable way to get the hand on the aggregated change events similiar to author of the above issue.

I also think to block the thread as the method doc indicates is a bit harsh.

I use

using var watcher = fileSystem.FileSystemWatcher.New("/");
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;

const string fileName = "test.txt"

List<WatcherChangeTypes?> changes = [];

using var changeWatcher = fileSystem.Watcher.OnTriggered(x => {
    if (x.Path.EndsWith(fileName)) {
        changes.Add(x.ChangeType);
    }
});

..operate on file system..

const int timeoutInSeconds = 5 * 1000;
changeWatcher.Wait(timeout: timeoutInSeconds);

// Assumptions
changes.FirstOrDefault().ShouldBe(WatcherChangeTypes.Created);
changes.LastOrDefault().ShouldBe(WatcherChangeTypes.Deleted);

Whatever timeout I choose, the wait timeout is exhausted no matter what leading to an timeout exception. Only if I set a breakpoint between "..operate on file system.." and "changeWatcher.Wait" it works as inteded. Pretty sure the notifications are not buffered / not replayed!?

Can we please enable the following workflow:

using var watcher = fileSystem.FileSystemWatcher.New("/");
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;

const string fileName = "test.txt"

List<WatcherChangeTypes?> changes = [];

using var changeWatcher = fileSystem.Watcher.OnTriggered(x => {
    if (x.Path.EndsWith(fileName)) {
        changes.Add(x.ChangeType);
    }
});

const int timeoutInSeconds = 5 * 1000;
// count = 3 (Created -> Changed -> Deleted)
var changeWatchedTask = changeWatcher.WaitAsync(timeout: timeoutInSeconds, count: 3);

..operate on file system..

// Assumptions
await changeWatchedTask; // if timeout exhausts throw as usual
changes.FirstOrDefault().ShouldBe(WatcherChangeTypes.Created);
changes.LastOrDefault().ShouldBe(WatcherChangeTypes.Deleted);

Here the corresponding extension method making above drawn workflow work:

file static class AwaitableChangeDescriptionCallbackExtensions
{
    extension<TValue>(IAwaitableCallback<TValue> awaitableCallback)
    {
        public Task WaitAsync(
            Func<TValue, bool>? filter = null,
            int timeout = 30000,
            int count = 1,
            Action? executeWhenWaiting = null,
            CancellationToken cancellationToken = default)
        {
            return Task.Factory.StartNew(
                static state => {
                    var (awaitableCallback, filter, timeout, count, executeWhenWaiting, cancellationToken) =
                        (ValueTuple<IAwaitableCallback<TValue>, Func<TValue, bool>?, int, int, Action?, CancellationToken>)state!;
                    
                    cancellationToken.ThrowIfCancellationRequested();

                    awaitableCallback.Wait(
                        filter,
                        timeout: timeout,
                        count: count,
                        executeWhenWaiting is not null ? ExecuteWhenWaiting : null);

                    return;

                    void ExecuteWhenWaiting()
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        executeWhenWaiting();
                    }
                },
                (awaitableCallback, filter, timeout, count, executeWhenWaiting, cancellationToken),
                cancellationToken,
                TaskCreationOptions.DenyChildAttach,
                TaskScheduler.Default);
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions