Is there a way to know how much buffer is left for FileSystemwatcher?

时光总嘲笑我的痴心妄想 提交于 2021-01-27 16:09:12

问题


When I query

fileSystemWatcher.InternalBufferSize

It will give the total Internal buffer size allocated to the Watcher. But I want to know (during debugging) how much buffer size for the Watcher is left and can be used and when I use the above statement in the Event handler method (say for the write operation) it always gives me the total buffer allocated size to the Watcher. Is there any way to obtain the remaining size of the buffer?

Other Questions:

From this answer, it is clear that event is handled on the separate thread than the thread which received the event. Suppose we have many concurrent events coming for a single Watcher which is watching the file. What I think (correct me if I am wrong) the main thread which received the event information will spawn a new thread for each event and processing of events will happen on different threads. So I want to ask:

  1. Will the main thread wait to finish the processing of all the events?
  2. Which thread will clear the internal buffer associated with the Watcher and when?
  3. I have read at lots of places that the handler method should take as minimum time as possible or we can get InternalBufferOverflow Exception. So, is it safe to assume that the Internal Buffer for the Watcher is only cleaned up when the thread(s) (I can't say one or all, but want to ask from you) which are processing the handler method has processed the method?

回答1:


No, you can't know how much buffer is left.

It is an implementation detail hidden in an internal class called FSWAsyncResult. If you get hold of an instance of that class and the buffer byte array it contains you still can't reliably answer how much space is left as that byte array only acts as reserved memory for the result of a call to ReadDirectoryChangesW

Find at the bottom of this answer a stripped down, reverse engineered version version of watching a folder for filechanges. Its logic and code matches what you'll find in the real FileSystemWatcher. I didn't bother to replace the magic constants with their proper meaning. It just works™. Don't forget to change the build setting unsafe as the code fiddles with pointers and native structures a lot. And I stripped out all error handling ...

If you follow below code, you'll notice that there is only one place where the byte[] buffer is created, and that only happens once. That same buffer is re-used. Reading the documentation, blogs and worker and I/O threads I understand that ReadDirectoryChangesW is used to issue a callback in an I/O completion fashion. It doesn't matter much for the managed world, that is just another thread.

The callback is scheduled on managed threadpool thread. Sometimes you'll get the same managed id you had before, when it is busy you get several. On that thread CompletionStatusChanged is executed. And that method is responsible for processing all the events that are present in the current byte buffer. Notice that I included a sizeused variable so you can see the actual size of valid data that was present in the buffer. For each event it found it raises/calls the subcribers of the events synchronously (so on the same thread). Once that is complete it calls Monitor again with the same byte[] buffer it just processed. Any file changes during the time CompletionStatusChanged is executing are kept by the OS and send the next time CompletionStatusChanged is called.

tl;dr; Here is a recap of the answers to your questions:

... I want to know (during debugging) how much buffer size for the Watcher is left and can be used

There is only one buffer used and it makes no sense to know how much is used or how much is left. Once your eventhandlers are called the buffer is reset and starts at 0 again. It raises an exception when there are more events then the byte buffer can handle.

  1. Will the main thread wait to finish the processing of all the events?

The OS will issue an asynchronous callback via an IOCompletionPort but that will manifest itself as normal managed threadpool thread. That thread will handle all events in the current buffer and call the eventhandlers.

  1. Which thread will clear the internal buffer associated with the Watcher and when?

The thread that executes the CompletionStatusChanged method. Notice in my testing the buffer was never cleared (as in filled with zeroes). Data was just overwritten.

  1. I have read at lots of places that the handler method should take as minimum time as possible or we can get InternalBufferOverflow Exception. So, is it safe to assume that the Internal Buffer for the Watcher is only cleaned up when the thread(s) (I can't say one or all, but want to ask from you) which are processing the handler method has processed the method?

You should keep your processing as short as possible because there is only one thread that will call all eventhandlers and in the end it has to call ReadDirectoryChangesW again. During this time it will keep track of filechanges. When those filechange events don't fit in the buffer it will raise an InternalBufferOverflow the next time the completion method is called.

Setup

A simple console app, with a ReadLine to keep it running while waiting for events.

static object instance = new object(); // HACK
static SafeFileHandle hndl; // holds our filehandle (directory in this case)

static void Main(string[] args)
{

    // the folder to watch
    hndl = NativeMethods.CreateFile(@"c:\temp\delete", 1, 7, IntPtr.Zero, 3, 1107296256, new SafeFileHandle(IntPtr.Zero, false));
    // this selects IO completion threads in the ThreadPool
    ThreadPool.BindHandle(hndl);

    // this starts the actual listening
    Monitor(new byte[4096]);

    Console.ReadLine();

}

Monitor

This method is responsible for creating the Native structures and an instance of a helper class to act as IAsyncResult implementation.
This method also calls ReadDirectoryChangesW and it chooses the combination of parameters that sets it up for asynchronous completion, with IOCompletinPorts. More background on those options can be found in Understanding ReadDirectoryChangesW - Part 1

static unsafe void Monitor(byte[] buffer)
{

    Overlapped overlapped = new Overlapped();

    // notice how the buffer goes here as instance member on AsyncResult.
    // Arrays are still Reference types.      
    overlapped.AsyncResult = new AsyncResult { buffer = buffer };
    // CompletionStatusChanged is the method that will be called
    // when filechanges are detected
    NativeOverlapped* statusChanged = overlapped.Pack(new IOCompletionCallback(CompletionStatusChanged), buffer);

    fixed (byte* ptr2 = buffer)
    {
        int num;
        // this where the magic starts
        NativeMethods.ReadDirectoryChangesW(hndl, 
          new HandleRef(instance, (IntPtr)((void*)ptr2)), 
          buffer.Length, 
          1, 
          (int)(NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Attributes), 
          out num, 
          statusChanged, 
          new HandleRef(null, IntPtr.Zero));
    }

}

CompletionStatusChanged

The CompletionStatusChanged method is called by the OS as soon as a filechange is detected. In the Overlapped structure we will find, after unpacking, our earlier ResultAsync instance with a filled buffer. The remainder of the method then decodes the byte array by reading the offset for any following events in the array as well as flags and the filename.

// this gets called by a ThreadPool IO Completion thread
static unsafe void CompletionStatusChanged(uint errorCode, uint numBytes, NativeOverlapped* overlappedPointer)
    {
        var sb = new StringBuilder();

        Overlapped overlapped = Overlapped.Unpack(overlappedPointer);
        var result = (AsyncResult) overlapped.AsyncResult;

        var position = 0;
        int offset;
        int flags;
        int sizeused = 0;
        string file;
        // read the buffer,
        // that can contain multiple events
        do
        {
            fixed (byte* ptr = result.buffer)
            {
                // process FILE_NOTIFY_INFORMATION
                // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx
                offset = ((int*)ptr)[position / 4];
                flags = ((int*)ptr + position / 4)[1];
                int len = ((int*)ptr + position / 4)[2];
                file = new string((char*)ptr + position / 2 + 6, 0, len / 2);
                sizeused = position + len + 14; 
            }
            sb.AppendFormat("#thread {0},  event: {1}, {2}, {3}, {4}\r\n", Thread.CurrentThread.ManagedThreadId, position, offset, flags, file);
            // in the real FileSystemWatcher here the several events are raised
            // so that uses the same thread this code is on.
            position += offset;
        } while (offset != 0);

        // my own logging
        sb.AppendFormat(" === buffer used: {0} ==== ", sizeused);

        Console.WriteLine(sb);

        // start again, reusing the same buffer:
        Monitor(result.buffer);
    }
}

Helper methods

The AsyncResult implements IAsyncResult (all empty) and holds the member to the byte array buffer.
The NativeMethods are exactly what they are called: entry points for Native calls into the WinAPI.

class AsyncResult : IAsyncResult
{
    internal byte[] buffer;
    // default implementation of the interface left out

    // removed default implementation for brevity
}

static class NativeMethods
{
    [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
    public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, SafeFileHandle hTemplateFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public unsafe static extern bool ReadDirectoryChangesW(SafeFileHandle hDirectory, HandleRef lpBuffer, int nBufferLength, int bWatchSubtree, int dwNotifyFilter, out int lpBytesReturned, NativeOverlapped* overlappedPointer, HandleRef lpCompletionRoutine);
}


来源:https://stackoverflow.com/questions/45586630/is-there-a-way-to-know-how-much-buffer-is-left-for-filesystemwatcher

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!