9

I'm watching a directory by calling ReadDirectoryChangesW synchronously. When a new file is available, I try to access it immediately with CreateFile with GENERIC_READ and FILE_SHARE_READ, but this gives me ERROR_SHARING_VIOLATION. The process that put the file in the watched directory does not finish writing by the time I try to read it.

Is there any way to reliably wait until the file is available for reading? I can put the method into a loop like the one below, but I'm hoping there's a better way.

while ((hFile = CreateFile (path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
{
    if (GetLastError() == ERROR_SHARING_VIOLATION)
        Sleep (500);
    else
        break; // some other error occurred
}

if (hFile == INVALID_HANDLE_VALUE)
{
    // deal with other error
    return 0;
}

ReadFile (...);
tshepang
  • 12,111
  • 21
  • 91
  • 136
dreamlax
  • 93,976
  • 29
  • 161
  • 209

4 Answers4

8

I don't think there is a notification for the kind of event you're looking for, but as an improvement, I'd suggest progressive delays. This way you will get fast response times for stuff like a drag/drop and won't hog the CPU with a tight loop if the user keeps the file open for an hour in Excel.

int delay= 10;
while ((hFile = CreateFile (path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
{
    if (GetLastError() == ERROR_SHARING_VIOLATION) {
        Sleep (delay);
        if (delay<5120) // max delay approx 5.Sec
            delay*= 2;
    }
    else
        break; // some other error occurred
}
Nicholaz
  • 1,419
  • 9
  • 11
2

There's no user-mode API for notifications on a closed file that I'm aware of. The loop you've proposed is really probably the best way. The only other thing you could do would be to watch for CloseFile in a filter driver ala Process Monitor, but yuck...

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
nitzmahone
  • 13,720
  • 2
  • 36
  • 39
2

As @Matt Davis said, there is unfortunately no user-mode API but there is a workaround that depending on your use-case (I've written mine below) may do just what you want.

What worked for me in the past was registering for FILE_NOTIFY_CHANGE_LAST_WRITE instead of FILE_NOTIFY_CHANGE_FILE_NAME when calling ReadDirectoryChangesW:

ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.hEvent = hChangeEvent;

// ...    
ReadDirectoryChangesW(hSpoolPath,
                      eventBuffer,
                      EVENT_BUF_LENGTH,
                      FALSE,
                      FILE_NOTIFY_CHANGE_LAST_WRITE, // <----
                      NULL,
                      &overlapped,
                      NULL);
// ...
HANDLE events[2];

events[0] = hChangeEvent;
events[1] = hCancelEvent;

DWORD wRc = WaitForMultipleObjects(2, events, FALSE, DIRECTORY_WATCH_TIMEOUT);

The last write time gets updated as soon as the owning process closes the handle after creating the file and writing to it.

My use-case was one process that received HTTP-requests via TCP/IP and wrote the HTTP-body into a directory, where another process picked it up as soon as the receiving process was finished writing (and consequently closing the handle) it. The http-server was the only process that wrote to that directory, so I could rely on the create-write-close pattern.

lx.
  • 2,317
  • 1
  • 22
  • 32
  • ReadDirectoryChangesW notifies me twice when I sign up for FILE_NOTIFY_CHANGE_LAST_WRITE and then add a file to the folder. Which notification did you use? – Will Aug 12 '17 at 18:36
0

If you know something about how the file is created, maybe wait until the file stops growing for X seconds, or wait until a sentinel file is deleted. Or sense the state of the program which creates them.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • Unfortunately I have no clue about how the file is generated or the process that is putting it in the watched folder (it could be any program, or it could be the user dragging and dropping a file, etc). – dreamlax Nov 17 '09 at 05:56