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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions