ReadDirectoryChangesW fails with error 995 if CancelIo was called before

528 Views Asked by At

I'm puzzled with the strange behavior of ReadDirectoryChangesW failing with error 995. Scenario explained below.

  1. FileHandle was obtained using CreateFileW.

  2. FileHandle obtained in step1 was used in ReadDirectoryChangesW. It success and sends the request to server

  3. Poll for 10 seconds and if no change notify was generated by server, cancel the chnagenotify request using cancelIo. It sends cancel & server responds.

  4. Now again setting change notify with ReadDirectoryChangesW using the file handle obtained in step1, it fails with "995 - The I/O operation has been aborted because of either a thread exit or an application request." No actual request was sent to server by this step.

  5. Immediately again call ReadDirectoryChangesW using the file handle obtained in step1 & it succeeds and sends request to server.

steps 3,4,5 are repeated in a loop & every alternate ReadDirectoryChangesW fails with 995 and immediate next one succeeds.

Can anyone tell me whats going on ? Below is the Code

void setnotify(WCHAR* _path)
{
    OVERLAPPED _overlapped;
    HANDLE     _handle;
    char       _buffer[8192] = {0};
    DWORD      _bufferSize = 8192;
    CnState    _state = CN_READY;
    DWORD      _inactivityTime = 0;

    typedef enum State
    {
        CN_READY,
        CN_REQUEST_PENDING,
        CN_RESPONSE_RECEIVED,
        CN_REQUEST_CANCELLED
    } CnState;

    _handle = CreateFileW(_path,
            GENERIC_READ, // access
            FILE_SHARE_READ |
            FILE_SHARE_WRITE |
            FILE_SHARE_DELETE, // share
            NULL, // sec
            OPEN_EXISTING, // disp
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // flags
            0);
    if (_handle == INVALID_HANDLE_VALUE)
    {
        exit(-1);
    }

    memset(&_overlapped, 0, sizeof(OVERLAPPED));

    if (!ReadDirectoryChangesW(_handle,
                _buffer,
                _bufferSize,
                true,
                0x255,
                NULL,
                &_overlapped,
                NULL)) {

        exit(-1);
    } else {
        _state = CN_REQUEST_PENDING;
        wprintf(L"Sent Change notify to Server\n");
    }


    while (1)
    {
        if ((_state == CN_REQUEST_PENDING) && (HasOverlappedIoCompleted(&_overlapped))) {
            wprintf(L"Response Received from Server\n");
            _state = CN_RESPONSE_RECEIVED;
        }

        if ((_state == CN_RESPONSE_RECEIVED) || (_state == CN_REQUEST_CANCELLED)) {
            memset(&_overlapped, 0, sizeof(OVERLAPPED));
            _inactivityTime = 0;
            if (!ReadDirectoryChangesW(_handle,
                        _buffer,
                        _bufferSize,
                        true,
                        255,
                        NULL,
                        &_overlapped,
                        NULL)) {

                wprintf(L"Sent Change notify to Server Failed.\n");
            } else {
                wprintf(L"Sent Change notify to Server\n");
                _state = CN_REQUEST_PENDING;
            }
        }

        if ((_state == ChangeNotifyRequest::CN_REQUEST_PENDING) &&
                (_inactivityTime >= 5000)){
            if (CancelIo(_handle)) {
                _state = CN_REQUEST_CANCELLED;
                wprintf(L"Cancelled Pending Requests.\n");
            } else {
                wprintf(L"Cancelled failed");
            }

        }

        Sleep(50);
        _inactivityTime += 50;

    }
}

Below is the Sample O/P:

Sent Change notify to Server

Cancelled Pending Requests.

Sent Change notify to Server

Cancelled Pending Requests.

Sent Change notify to Server Failed.

Sent Change notify to Server

Cancelled Pending Requests.

Sent Change notify to Server Failed.

Sent Change notify to Server

Cancelled Pending Requests.

Sent Change notify to Server Failed.

Sent Change notify to Server

1

There are 1 best solutions below

15
Remy Lebeau On

You start an operation and then cancel it, so its completion event will report back an ERROR_OPERATION_ABORTED (995) error. But, you are starting a new operation before you have received that event. When you call CancelIo(), it is simply a request to cancel, the original operation is still pending, and may take awhile to actually cancel (or it may complete successfully before the cancellation request is processed). So, you still need to wait for the cancelled operation to actually complete, and then handle the result whether good or bad, before you then start the next operation.

Also, there are two other bugs in your code.

When calling ReadDirectoryChangesW() for the first time, you are setting the dwNotifyFilter parameter to 0x255, which is wrong. You are effectively requesting only these filter bits:

FILE_NOTIFY_CHANGE_FILE_NAME
FILE_NOTIFY_CHANGE_ATTRIBUTES
FILE_NOTIFY_CHANGE_LAST_WRITE
FILE_NOTIFY_CHANGE_CREATION

Subsequent calls are setting the dwNotifFilter to 255 instead, which is effectively requesting these filter bits:

FILE_NOTIFY_CHANGE_FILE_NAME
FILE_NOTIFY_CHANGE_DIR_NAME
FILE_NOTIFY_CHANGE_ATTRIBUTES
FILE_NOTIFY_CHANGE_SIZE
FILE_NOTIFY_CHANGE_LAST_WRITE
FILE_NOTIFY_CHANGE_LAST_ACCESS
FILE_NOTIFY_CHANGE_CREATION

So, your filtering is inconsistent. You really shouldn't be using "magic numbers" in the first place. The Win32 API has #define constants for the available flags, you should be using them the way they are intended.

Lastly, you are not associating an Event object from CreateEvent() with the OVERLAPPED structure. This requirement is clearly stated in the ReadDirectoryChangesW() documentation when you are not using an I/O Completion Port or I/O Completion callback.

Try something more like this instead:

void setnotify(WCHAR* _path)
{
    typedef enum State
    {
        CN_READY,
        CN_REQUEST_PENDING,
        CN_REQUEST_COMPLETE
    } CnState;

    OVERLAPPED  _overlapped = {0};
    HANDLE      _handle;
    char        _buffer[8192];
    DWORD       _bufferSize;
    CnState     _state = CN_READY;
    DWORD       _inactivityTime;
    const DWORD _filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION;

    _handle = CreateFileW(_path,
            GENERIC_READ, // access
            FILE_SHARE_READ |
            FILE_SHARE_WRITE |
            FILE_SHARE_DELETE, // share
            NULL, // sec
            OPEN_EXISTING, // disp
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // flags
            0);
    if (_handle == INVALID_HANDLE_VALUE)
    {
        wprintf(L"Opening Server failed. Error: %u\n", GetLastError());
        exit(-1);
    }

    _overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (_overlapped.hEvent == NULL)
    {
        wprintf(L"Creating Overlapped Event failed. Error: %u\n", GetLastError());
        exit(-1);
    }

    do
    {
        switch (_state)
        {
            case CN_READY:
            {
                _bufferSize = 0;
                _inactivityTime = 0;

                if (!ReadDirectoryChangesW(_handle,
                        _buffer,
                        sizeof(_buffer),
                        TRUE,
                        _filter,
                        &_bufferSize,
                        &_overlapped,
                        NULL))
                {
                    wprintf(L"Requesting change notify from Server failed. Error: %u\n", GetLastError());
                    exit(-1);
                }

                _state = CN_REQUEST_PENDING;
                wprintf(L"Change notify requested from Server\n");

                break;
            }

            case CN_REQUEST_PENDING:
            {
                if (HasOverlappedIoCompleted(&_overlapped))
                {
                    _state = CN_REQUEST_COMPLETE;
                }
                else if (_inactivityTime >= 5000)
                {
                    if (CancelIo(_handle))
                    {
                        _state = CN_REQUEST_COMPLETE;
                        wprintf(L"No response in 5 seconds. Cancelling pending request\n");
                    }
                    else
                        wprintf(L"No response in 5 seconds. Cancelling pending request failed. Error: %u\n", GetLastError());
                }
                else
                {
                    Sleep(50);
                    _inactivityTime += 50;
                }

                break;
            }

            case CN_REQUEST_COMPLETE:
            {
                if (GetOverlappedResult(_handle, &_overlapped, &_bufferSize, TRUE))
                {
                    wprintf(L"Response received from Server\n");
                    // use _buffer up to _bufferSize bytes as needed...
                }
                else if (GetLastError() == ERROR_OPERATION_ABORTED)
                {
                    wprintf(L"Pending request cancelled\n");
                }
                else
                {
                    wprintf(L"Change notify from Server failed. Error: %u\n", GetLastError());
                    // handle error as needed...
                }

                _state = CN_READY:
                break;
            }
        }
    }
}

However, if you are not going to use an I/O Completion Port or I/O Completion callback, you can greatly simplify the code by utilizing the fact that you can more effectively wait on the OVERLAPPED result by waiting on the Event object to be signaled, without having to poll the OVERLAPPED status in a loop at all:

void setnotify(WCHAR* _path)
{
    OVERLAPPED  _overlapped = {0};
    HANDLE      _handle;
    char        _buffer[8192];
    DWORD       _bufferSize;
    const DWORD _filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION;

    _handle = CreateFileW(_path,
            GENERIC_READ, // access
            FILE_SHARE_READ |
            FILE_SHARE_WRITE |
            FILE_SHARE_DELETE, // share
            NULL, // sec
            OPEN_EXISTING, // disp
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // flags
            0);
    if (_handle == INVALID_HANDLE_VALUE)
    {
        wprintf(L"Opening Server failed. Error: %u\n", GetLastError());
        exit(-1);
    }

    _overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (_overlapped.hEvent == NULL)
    {
        wprintf(L"Creating Overlapped Event failed. Error: %u\n", GetLastError());
        exit(-1);
    }

    do
    {
        _bufferSize = 0;

        if (!ReadDirectoryChangesW(_handle,
            _buffer,
            sizeof(_buffer),
            TRUE,
            _filter,
            &_bufferSize,
            &_overlapped,
            NULL))
        {
            wprintf(L"Requesting change notify from Server failed. Error: %u\n", GetLastError());
            exit(-1);
        }

        wprintf(L"Change notify requested from Server\n");

        // alternatively, use GetOverlappedResultEx() with a timeout
        // instead of WaitForSingleObject() and GetOverlappedResult()
        // separately...

        if (WaitForSingleObject(_overlapped.hEvent, 5000) == WAIT_TIMEOUT)
        {
            if (CancelIo(_handle))
                wprintf(L"No response in 5 seconds. Cancelling pending request\n");
            else
                wprintf(L"No response in 5 seconds. Cancelling pending request failed. Error: %u\n", GetLastError());
        }

        if (GetOverlappedResult(_handle, &_overlapped, &_bufferSize, TRUE))
        {
            wprintf(L"Response received from Server\n");
            // use _buffer up to _bufferSize bytes as needed...
        }
        else if (GetLastError() == ERROR_OPERATION_ABORTED)
        {
            wprintf(L"Pending request cancelled\n");
        }
        else
        {
            wprintf(L"Change notify from Server failed. Error: %u\n", GetLastError());
            // handle error as needed...
        }
    }
    while (true);
}

Also, see my earlier answer to a similar question, which explains some other gotchas you have to be aware of when using ReadDirectoryChangesW(), particularly handling of the ERROR_NOTIFY_ENUM_DIR error.