Hello @iamoon-6427 ,
The difference is not caused by dwMilliseconds. It is caused by where ReadDirectoryChangesExW sits in your loop. It is called at the top of while (true), so it runs again after every WAIT_TIMEOUT, while the previous request is still pending, using the same handle, the same buffer and the same OVERLAPPED.
With a 20 ms timeout the loop issues a new request every 20 ms while the directory is idle. When neovim saves the file, the kernel completes several of those queued requests, each writing a batch of notifications into the same buffer, so later batches overwrite earlier ones. The auto-reset event is signaled once, you call GetOverlappedResult once and parse only what is left in the buffer. That is why the early events (4913 ADDED, RENAMED_OLD_NAME, RENAMED_NEW_NAME) are gone.
With INFINITE the wait only returns after the request completes, so there is always exactly one outstanding request and nothing is overwritten.
The documentation calls this out directly:
A common mistake is to reuse an OVERLAPPED structure before the previous asynchronous operation has been completed. You should use a separate structure for each request. OVERLAPPED structure, Remarks
I reproduced this with your code by counting issued vs. harvested requests and generating the same sequence neovim produces on :w after the watcher had been idle for 3 seconds. With INFINITE, 0 requests were pending when the change happened and all events were received. With 20, about 90 requests were pending, only 1 completion was harvested and only 1 event (b.txt~ REMOVED) was received. With the corrected loop below and the same 20 ms timeout, all events were received. Two runs, identical results.
Fix: issue one request, keep it outstanding across timeouts, and issue the next one only after the previous result has been consumed.
auto post = [&]() {
return ::ReadDirectoryChangesExW(hDir, buffer, SIZE_BUF, FALSE, dwNotifyFilter,
NULL, &ol, NULL, ReadDirectoryNotifyExtendedInformation);
};
if (!post()) { /* handle error */ }
while (true) {
DWORD res = ::WaitForSingleObject(ol.hEvent, 20);
if (res == WAIT_TIMEOUT) {
// do other work, then keep waiting on the SAME request
continue;
}
if (res != WAIT_OBJECT_0) { /* handle error */ }
DWORD szTransferred = 0;
if (!::GetOverlappedResult(hDir, &ol, &szTransferred, FALSE)) { /* handle error */ }
if (szTransferred == 0) { /* internal buffer overflowed, re-enumerate the directory */ }
// parse FILE_NOTIFY_EXTENDED_INFORMATION entries
if (!post()) { /* handle error */ } // only now issue the next request
}
A few related points:
- Use a manual-reset event for
OVERLAPPED.hEvent. Wait functions andGetOverlappedResultboth reset auto-reset events, which can makeGetOverlappedResult(..., TRUE)hang after a successful wait (OVERLAPPED, hEvent). Also drop the"WatchEvent"name, a named event is system-wide and can collide with another process. - To stop while a request is pending, call
CancelIoEx(hDir, &ol)and thenGetOverlappedResultonce more before closing the handle. - 0 bytes from
GetOverlappedResult, orERROR_NOTIFY_ENUM_DIR, means the internal buffer overflowed and you must rescan the directory (ReadDirectoryChangesExW, Remarks). -
WaitForSingleObject/WaitForMultipleObjects+GetOverlappedResultis a supported pattern and is what the wsdapi sample does. The sample works because it keeps one outstanding request per handle.
Hope these information help! If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.
Thank you.