dwMilliseconds is not what changes the set of file notifications. The difference comes from how the overlapped operation is being consumed.
For ReadDirectoryChangesExW opened with FILE_FLAG_OVERLAPPED, the documented async patterns are:
-
GetOverlappedResultwith a uniqueOVERLAPPED.hEvent -
GetQueuedCompletionStatuswith an I/O completion port - A completion routine, which requires the thread to be in an alertable wait state
In the first pattern, the event is only the completion signal. The actual result must be obtained from the same overlapped operation.
The key issue in the shown loop is this sequence:
- start
ReadDirectoryChangesExW - call
WaitForSingleObject(ol.hEvent, 20) - if timeout, immediately loop and issue another
ReadDirectoryChangesExWagain on the same handle with the sameOVERLAPPED
That means a previous async directory read can still be pending when the next iteration starts. The code only calls GetOverlappedResult in the WAIT_OBJECT_0 branch, so timed-out iterations do not complete or harvest the pending operation before reissuing another read. With INFINITE, the code always waits until the operation completes before continuing, so notifications are not skipped by the loop logic.
Also, ReadDirectoryChangesExW keeps a buffer associated with the directory handle. Changes between calls are added to that buffer and returned on the next call. If the system cannot record all changes, or if the buffer is too small for all detailed changes, the documented recovery is to enumerate the directory or subtree. So event grouping can differ between calls, and missing detailed notifications must be handled by rescan logic.
Recommended fixes:
- Do not issue a new
ReadDirectoryChangesExWuntil the previous overlapped request has completed and its result has been collected. - If using event-based completion, keep the pattern as:
- issue one async
ReadDirectoryChangesExW - wait for
ol.hEvent - call
GetOverlappedResult - parse all returned entries
- issue the next async
ReadDirectoryChangesExW
- issue one async
- If a timeout is needed, use it only to let the thread do other work, not to start another directory read while one is still pending.
- If using completion routines, use an alertable wait such as
WaitForSingleObjectEx, becauseWaitForSingleObjectdoes not enter an alertable wait state. - Use a manual-reset event in the
OVERLAPPEDstructure when retrieving overlapped results withGetOverlappedResultEx; the documentation warns about auto-reset events causing indefinite blocking in some wait/result sequences.
For the current code, the simplest correction is:
- keep one outstanding
ReadDirectoryChangesExW - on
WAIT_TIMEOUT, do other work and then wait again on the same outstanding request - only after
WAIT_OBJECT_0and successfulGetOverlappedResultshould the nextReadDirectoryChangesExWbe posted
That explains why INFINITE appears to “work” and 20 appears to “lose” events: the timeout path is allowing the loop to outrun the pending overlapped request, not changing the semantics of directory notifications.
References: