1

I have a problem that ReadDirectoryChangesW keeps missing events.

I did a lot of googling and the bellow function arguments seem correct according to my searches, but nobody knows for certain. I start watching like this.

BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
_ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);

_directoryHandle = ::CreateFileA("some path here", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

// This should be quite enough to fit multiple file events
static constexpr DWORD ResultDataLength = 10000;
// Byte size used for winapi calls and memcpy during move operation
static constexpr DWORD ResultDataByteSize = ResultDataLength * sizeof(FILE_NOTIFY_INFORMATION);
FILE_NOTIFY_INFORMATION _resultData[ResultDataLength] = { 0 };

_watchRequestResult = ::ReadDirectoryChangesW(
  _directoryHandle,
  (LPVOID)_resultData,
  ResultDataByteSize,
  TRUE,
  FILE_NOTIFY_CHANGE_FILE_NAME,
  NULL,
  &_ovl,
  NULL
);

After the above, I wait for the _ovl.hEvent using WaitForMultipleObjects. I use multiple objects, because there's always also the event I ise to tell the watch thread to quit.

If the ovl.hEvent is notified, I do this:

DWORD _ovlBytesReturned = 0;
// Imagine some struct that I use to pass the file info, not important how it looks
std::vector<MyFileInfoStruct> results;
if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
{
  int byteIndex = 0;
  bool previousWasRename = false;
  const int minSize = min(ResultDataLength, _ovlBytesReturned);
  while (byteIndex < minSize)
  {
    FILE_NOTIFY_INFORMATION* info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
    byteIndex += info->NextEntryOffset;

    // read the stuff in the info
    results.push_back(MyFileInfoStruct::FromFileInfo(info));

    // If next entry index is 0, it means there is no next entry
    if (info->NextEntryOffset == 0)
    {
      break;
    }
  }
}
// if file is renamed, merge new name and old name to same result. However rename works to give me two FILE_NOTIFY_INFORMATION that both contain expected data
MergeResultRename(results)
// results is always 1 item long

I should note at this point, that info->NextEntryOffset is not always 0 - if I rename a file, I correctly get two entries in _resultData, one for new filename and one for old.

What never get is multiple file changes per event. This is a problem, the whole code looks like this (pseudocode)

Let EVENTS be an array of HANDLE event objects
Let FILE_CHANGES be a buffer of file changes 
while(shouldBeWatching) 
{
  Wait for events from previous iteration stored in EVENTS array. Skip on first iteration.
  if(event has fired) 
  {
    if(event that fired is ovl.hEvent)
    {
      Put file changes from the event that fired into FILE_CHANGES array (seen in 2nd code sample above)
      Delete and close all handles related to the event:
         Close directory handle
         Close ovl.hEvent
    }
    else 
    {
      Close everything and quit thread.
    }
  }
  Start new request (seen above in 1st code sample)
  if(FILE_CHANGES is not empty) 
  {
    Process all info from FILE_CHANGES
  }
}

Now you can see that I restart the request for ReadDirectoryChangesW before I process the MyFileInfoStruct array. But the problem is, if more than two files are copied, the second file is registered by the event while I am processing the previous one, but the subsequent changes are ignored until I "pick up" the last change and restart the event.

I could fist this partially by having second thread to do the Process all info from FILE_CHANGES part. But that only reduces chance of missing the events by making the whole start request -> wait -> pick up -> restart event routine a bit faster. It does not actually provide 100% coverage, there is still a moment where no ReadDirectoryChangesW request is pending.

I've been reading a lot on the internet and found two solutions being mentioned often:

  • Use separate thread for processing file changes (I already did that)
  • Increase the size of the FILE_NOTIFY_INFORMATION[]. this doesn't work for me, windows only puts one event there

Thus the question is: How do I get ReadDirectoryChangesW and GetOverlappedResult to keep adding file changes in the FILE_NOTIFY_INFORMATION[] buffer until I "pick up" the results by calling GetOverlappedResult? is this even possible? Has anyone managed to get multiple results into one buffer?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 1
    Unrelated: The size of the buffer in `ReadDirectoryChangesW` should be in bytes. That is `ResultDataSize * sizeof(FILE_NOTIFY_INFORMATION)` or better: `sizeof(_resultData)`. I did the same mistake in my old answer to a similar question you asked. – Ted Lyngmo Jan 23 '20 at 18:58
  • 1
    ReadDirectoryChangesW uses an internal buffer, a request does not need to be pending for events to be captured. – Jonathan Potter Jan 23 '20 at 22:06
  • you not need use any separate threads. windows add all events to buffer, even if I/O request not active at this time (because you handle changes). if buffer too smal - you got `ERROR_NOTIFY_ENUM_DIR`. if you view another behavior - errors in your code – RbMm Jan 25 '20 at 00:08
  • *Close directory handle* - this is one of your error - not need do this - continue use already opened handle. - exactly because this you never view *What never get is multiple file changes per event* – RbMm Jan 25 '20 at 17:26
  • 1
    I rewrote my [old answer](https://stackoverflow.com/a/58509037/7582247) to address the mistake of reopening the directory for each iteration. – Ted Lyngmo Jan 25 '20 at 20:11

1 Answers1

3

For renaming a file there are two actions happened: FILE_ACTION_RENAMED_OLD_NAME and FILE_ACTION_RENAMED_NEW_NAME. These two action events you can retrieve via once calling of ReadDirectoryChanges and GetOverlappedResult as you already have done.

enter image description here

enter image description here

I have a problem that ReadDirectoryChangesW keeps missing events.

To capture events like copy two files and delete two files, for example, firstly I copy TESTA.txt and TESTB.txt to directory D:\testFolder, then delete them both. I can get all events by calling ReadDirectoryChanges and GetOverlappedResult in a while loop. Both functions called by four times for fours events.

enter image description here

Test code as follows:

#include <windows.h>
#include <vector>

using namespace std;

typedef struct TEST_INFO {
    DWORD NextEntryOffset;
    DWORD Action;
    DWORD FileNameLength;
    WCHAR FileName[100];
}_TEST_INFO;

int main()
{
    BOOL _watchRequestResult = false;
    OVERLAPPED _ovl = { 0 };
    _ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);

    HANDLE _directoryHandle = ::CreateFileA("d:\\testFolder", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

    // This should be quite enough to fit multiple file events
    static constexpr DWORD ResultDataSize = 100;
    _TEST_INFO _resultData[ResultDataSize] = { 0 };

    while (true)
    {

        _watchRequestResult = ::ReadDirectoryChangesW(
            _directoryHandle,
            (LPVOID)_resultData,
            ResultDataSize * sizeof(_TEST_INFO),
            TRUE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL,
            &_ovl,
            NULL
        );

        DWORD _ovlBytesReturned = 0;

        if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
        {
            int byteIndex = 0;

            while (TRUE)
            {
                _TEST_INFO* info = reinterpret_cast<_TEST_INFO*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
                byteIndex += info->NextEntryOffset;

                wprintf(L"File name: %s, ", info->FileName);
                printf("Action: ");
                switch (info->Action)
                {
                case FILE_ACTION_ADDED:
                    printf("Added \n");
                    break;
                case FILE_ACTION_REMOVED:
                    printf("Removed \n");
                    break;
                case FILE_ACTION_MODIFIED:
                    printf("Modified \n");
                    break;
                case FILE_ACTION_RENAMED_OLD_NAME:
                    printf("Rename old name \n");
                    break;
                case FILE_ACTION_RENAMED_NEW_NAME:
                    printf("Rename new name \n");
                    break;
                }

                // If next entry index is 0, it means there is no next entry
                if (info->NextEntryOffset == 0)
                {
                    break;
                }
            }
        }

    }

    getchar();
}

Summary:

GetOverlappedResult function:

The results reported by the GetOverlappedResult function are those of the specified handle's last overlapped operation.

So renaming a file is an overlapped (rename) operation and copying a file is an overlapped (copy) operation. However copying two files are two overlapped (copy) operations. So it requires calling GetOverlappedResult two times instead of one.

Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • Ok, thanks for clearing that up. But obviously I still have the problem of missing events, so do you happen to know how to make sure that there's a request queued 100% of time? – Tomáš Zato Jan 24 '20 at 09:05
  • If you do the operations really quickly, you'll get many operation per call to `GetOverlappedResult`- I assume all operations that `ReadDirectoryChanges` has buffered are moved into your supplied buffer (as many that fits in the buffer that is). – Ted Lyngmo Jan 24 '20 at 10:57
  • for what you use `FILE_FLAG_OVERLAPPED` on file when you not do asynchronous io?! – RbMm Jan 24 '20 at 20:54
  • *However copying two files are two overlapped (copy) operations.* ?? what this is mean ? how your text after **Summary:** at all related – RbMm Jan 25 '20 at 00:10
  • @RbMm I'm not sure I get what you mean by you summary, but OP suggests using an event handle to be able to break out of a `WaitForMultipleObjects` on the users request. In this particual API, using `OVERLAPPED` also means telling the API to care for buffering internally (non-Win32 like). I got tricked by it. Overlapped in Windows has meant that I need to juggle buffers myself - but this API seems to handle it all internally. – Ted Lyngmo Jan 25 '20 at 03:12
  • @TedLyngmo i mean that text at the end of answer is wrong and unrelated. *In this particular API, using OVERLAPPED also means telling the API to care for buffering internally* - this is wrong. at first not using `OVERLAPPED` but `FILE_FLAG_OVERLAPPED` at second use this only tell not wait in kernel but return just. some buffer holding in kernel, after first call api, so usually we not lost events.. and all this code(in question and answer) not asynchronous by sense. – RbMm Jan 25 '20 at 09:42
  • @RbMm The combination of using `CreateFile(...FILE_FLAG_OVERLAPPED...);` and `ReadDirectoryChanges(...LPOVERLAPPED...);` is what makes it work with OP:s need to be able to monitor a second handle (the one telling the thread to stop monitoring directory changes). Even if this answer does not include it, it provides the base for adding `WaitForMultipleObjects()` to wait for either directory changes or the other events OP needs. But I agree in that, used as shown, it does not look necessary to use overlapped operations. The answer is halfway there so to speak. – Ted Lyngmo Jan 25 '20 at 15:05
  • @TedLyngmo - yes, possible use dedicated thread and wait on several events. i not like such solutions. i prefer work full asynchronous without dedicated threads. like [this](https://pastebin.com/asJhR9G4). also no "lost" changes - even if i special set *Sleep* for delay between `ReadDirectoryChangesW` calls - i not lost notifications but got **multiple actions** in next call. this is exactly about OP ask. not need separate thread for process changes(if this not take too big time). if OP always view only single event - error in it code. if some events lost - we got `ERROR_NOTIFY_ENUM_DIR` – RbMm Jan 25 '20 at 15:38
  • @RbMm OP uses another thead to set an event handle to signalled state to be able to abort waiting for directory changes. It's a pretty common setup afaik. Using a completion routine if or course an option if you don't already have threads and see no use for it, but I'm just reading what OP tells us about his setup. – Ted Lyngmo Jan 25 '20 at 16:49
  • @TedLyngmo - i view what use OP. one of it main errors - *Close directory handle* in this case really never get is multiple file changes per I/O request. need not close but still use already opened. in this case can be and multiple file changes and `ERROR_NOTIFY_ENUM_DIR` in case lost some events. and not need separate thread for handle changes. (p.s - i not use completion routine in example too. i use IOCP notification) – RbMm Jan 25 '20 at 17:32
  • @RbMm Yes exacly. OP did figure that part out eventually and wrote a comment about it to my old answer. I updated my old answer to try to compensate for the missing events - but that update wasn't needed. I will revert most of the changes I did later. – Ted Lyngmo Jan 25 '20 at 17:46