0

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

Asesh
  • 3,186
  • 2
  • 21
  • 31
skanyal
  • 1
  • 2
  • 2
    Please show your code rather than describing it. – Jonathan Potter Aug 18 '17 at 15:47
  • Thanks Jonathan. Added the code. – skanyal Aug 18 '17 at 16:40
  • are this your actual code, on which you get this result ?(for example no GetLastError() call in code). anyway you logic for asynchronous io is invalid. all need do in absolute another way – RbMm Aug 18 '17 at 17:57
  • @RbMm: I removed the unwanted code to keep it simple :). I did use GetLastError() in actual code: Below was the error: 995 - The I/O operation has been aborted because of either a thread exit or an application request. Anyways could you pls tell me another way using which i could solve this problem ? – skanyal Aug 18 '17 at 18:06
  • here some related to network redirector. this only happens when file open via unc path. but all logic for asynchronous io is invalid for my look. you need bind iocp to file handle or use apc routine. but not spin in function. and not cancel io (for what ?) when you no more need notification - close file handle (you got `ERROR_NOTIFY_CLEANUP` finally in callback) – RbMm Aug 18 '17 at 18:34
  • That is ERROR_OPERATION_ABORTED. Well, you did. Consider what would happen if it *didn't* work that way and the call completed without an error. How could you possibly tell the difference between it completing a nanosecond before you called CancelIo and it completing because you aborted it? Threading race bugs are not a winapi feature. – Hans Passant Aug 19 '17 at 22:54
  • @HansPassant - `CancelIo` cancel existing io requests (*IRP*) he have not (and can not) have effect to new IRP (which at this time even not created). and here nothing with race, threading etc. this look like "feature" or bug of concrete driver - `mup.sys` which remember that previous call was canceled, and cancel and new. – RbMm Aug 20 '17 at 13:28
  • even more - almost any asynchronous io never return this error just. it or return `STATUS_PENDING` or `STATUS_SUCCESS` or some other error. and only in completion can be `STATUS_CANCELLED` – RbMm Aug 20 '17 at 13:28

1 Answers1

2

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks Remy. How could i wait for the Cancelo() to actually finish before i send next ReadDirectoryChangesW (To avoid the failure i m seeing). It was surprising for me because every time i run this program, first time ReadDirectoryChangesW after cancelIo succeds (line 3 of the output), but after that every alternate request fails – skanyal Aug 18 '17 at 17:31
  • @skanyal get rid of your `CN_REQUEST_CANCELLED` logic, stay in the `CN_REQUEST_PENDING` state until `HasOverlappedIoCompleted()` reports otherwise, then handle the result of the finished operation as needed before calling `ReadDirectoryChangesW()` again. – Remy Lebeau Aug 18 '17 at 17:34
  • Still same Error even after processing cancelIo Resp: Sent Change notify to Server. Cancelled Pending Requests. Response Received from Server. Sent Change notify to Server . Cancelled Pending Requests. . Response Received from Server. Sent Change notify to Server Failed. Sent Change notify to Server. Cancelled Pending Requests. Response Received from Server. Sent Change notify to Server Failed. Sent Change notify to Server. – skanyal Aug 18 '17 at 17:50
  • this behavior somehow related to `\device\mup` (this happens only if file open via unc path). im my tests the operation just canceled after `CancelIo()` (say `HasOverlappedIoCompleted(&_overlapped)` always true after `Cancello()` without delay). and `GetOverlappedResult` with `bWait==TRUE` nothing changed. `ReadDirectoryChangesW` fail through one. i think problem here in invalid code logic + some bug(?) in mup – RbMm Aug 18 '17 at 18:45
  • @eryksun. If i understand correctly, what you are saying is to check if _overlapped.Internal == 0xC0000120 in the polling logic after cancelIo is sent & after that condition satisfies, then send the "ReadDirectoryChangesW". I tried that as well, but results are same. Did i miss something ? – skanyal Aug 18 '17 at 18:45
  • @skanyal - i check the same with more simply code and the same as you result. the `_overlapped.Internal == 0xC0000120` always after `CancelIo` however `ReadDirectoryChangesW` fail through one. i advice full change your code logic and use callbacks instead polling. and not cancel io at all. instead close file handle when you not need more notify – RbMm Aug 18 '17 at 18:48
  • @skanyal: I updated my answer with more details and code examples. – Remy Lebeau Aug 18 '17 at 18:50
  • @RbMm. Thanks for digging up further. Is there any way we can solve this ? May be can you provide a new logic :) ? I desperately need this thing to get working . – skanyal Aug 18 '17 at 18:50
  • @RemyLebeau - are you test this code (with `GetOverlappedResult`) on unc path (even local `L"\\\\localhost\\somefolder"`) ? - even despite we wait for operation finished inside `GetOverlappedResult` - anyway in my test `ReadDirectoryChangesW` fail through one – RbMm Aug 18 '17 at 18:56
  • @Remy. Thanks for the detail explaination. I used the sample code you provided but i see the same issue Change notify requested from Server. No response in 5 seconds. Cancelling pending request. Pending request cancelled. Change notify requested from Server. No response in 5 seconds. Cancelling pending request. Pending request cancelled. Requesting change notify from Server failed. Error: 995. – skanyal Aug 18 '17 at 19:06
  • @Remy:I forgot to mention another detail. The original code i posted works fine for a local system but it fails when its executed for the remote share drive. – skanyal Aug 18 '17 at 19:08
  • @skanyal - it fail even on local share folder (via `\\localhost` or `\\127.0.0.1` path ) and work with no errors with local file path. however - for what you cancel io at all ? not do this and use callbacks instead spin in function- this kill all asynchronous logic – RbMm Aug 18 '17 at 19:10
  • @Remy. Yes local drive path works !! I need cancelIO to automate a scenario. Anyways i even tried with the callback and was hitting same issue. Do you have any sample code that would work ? – skanyal Aug 18 '17 at 19:18
  • @RbMm: Closing the file handle is not an option for me :(. The scenario i m trying is to cancel the notifies and set the notifies again on the previously opened file. – skanyal Aug 18 '17 at 19:22
  • 1
    @skanyal - this is bug in `mup.sys`. code with cancel operation anyway will be not work as excepted. you say can ignore notifications instead cancelio. and for full stop it close file handle – RbMm Aug 18 '17 at 19:25