2

CancelIo() is supposed to cancel all pending I/O operations associated with the calling thread. In my experience, CancelIo() sometimes cancels future I/O operations as well. Given:

ReadFile(port, buffer, length, &bytesTransferred, overlapped);
  1. If I invoke CancelIo(port) immediately before the read, GetQueuedCompletionStatus() will block forever, never receiving the read operation.
  2. If I invoke CancelIo(port) immediately after the read, GetQueuedCompletionStatus() will return 0 with GetLastError()==ERROR_OPERATION_ABORTED
  3. If I invoke CancelIo(port) and there are no pending or subsequent reads, GetQueuedCompletionStatus() will block forever.

The key point here is that there is no way to detect when CancelIo() has finished executing. How can I ensure that CancelIo() is done executing and it is safe to issue further read requests?

PS: Looking at http://osdir.com/ml/lib.boost.asio.user/2008-02/msg00074.html and http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/using.html it sounds like CancelIo() is not really usable. Must customer requires Windows XP support. What are my options?

NOTE: I am reading from a serial port.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Gili
  • 86,244
  • 97
  • 390
  • 689

4 Answers4

4

CancelIo() works fine. I misunderstood my code.

Upon further investigation it turns out that the code was invoking CancelIo() followed by ReadFile() with a timeout INFINITE. The completion port was never getting notified of the read because the remote end was never sending anything. In other words, CancelIo() did not cancel subsequent operations.

I found some eye-opening documentation here:

Be careful when coding for asynchronous I/O because the system reserves the right to make an operation synchronous if it needs to. Therefore, it is best if you write the program to correctly handle an I/O operation that may be completed either synchronously or asynchronously. The sample code demonstrates this consideration.

It turns out that device drivers may choose to treat an asynchronous operation in a synchronous manner if the data being read is already cached by the device driver. Upon further investigation, I discovered that when CancelIo() was being invoked before ReadFile() it would sometimes cause the latter to return synchronously. I have no idea why the completion port was never getting notified of ReadFile() after a CancelIo() but I can no longer reproduce this problem.

The completion port is signaled regardless of whether ReadFile() is synchronous or asynchronous.

Gili
  • 86,244
  • 97
  • 390
  • 689
  • Which incidentally is covered by the advice in my answer. Synchronous completion of I/O will still cause `overlapped.Handle` to be signaled. – Ben Voigt Oct 17 '10 at 05:48
  • I've lost several hours trying to understand why `HasOverlappedIoCompleted` was still false after canceling, thanks to both @BenVoigt and you for the clarification. Waiting on `overlapped.hEvent` showed that iocp is signalled. – vgru Mar 25 '17 at 23:49
3

Wait on (possibly with zero timeout) overlapped.Handle. It will be set whether the operation is completed or cancelled.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • http://msdn.microsoft.com/en-us/library/aa363791%28VS.85%29.aspx says "CancelIO cancels only outstanding I/O on the handle, it does not change the state of the handle; this means that you cannot rely on the state of the handle because you cannot know whether the operation was completed successfully or canceled." – Gili Oct 15 '10 at 13:19
  • 1
    @Gili: The file HANDLE is different from the event HANDLE in the overlapped structure. That text refers to the file HANDLE. – Ben Voigt Oct 15 '10 at 15:25
-1

It is possible to write asynchronous I/O code without CancelIo function. The question depends on the scenario you are using CancelIO. Let's say that you need to implement file reading thread. Thread pseudo-code:

for(;;)
{
    ReadFile(port, buffer, length, &bytesTransferred, overlapped); 
    WaitForMultipleObjects( overlapped event + stop event);

    if ( stop event is signaled )
       break;

    if (overlapped event is signaled )
        handle ReadFile results
}

Such thread reads file (socket, port etc.) using overlapped I/O. Most of the time it waits on WiatForMultipleObjects line. It wakes up when new data is available, or stop event is signaled. To stop this thread, set stop event from another thread. CancelIO is not used.

Alex F
  • 42,307
  • 41
  • 144
  • 212
  • Is it possible to abort the read and continue using the handle without closing it? – Gili Oct 13 '10 at 06:50
  • You don't need to abort the read operation, just stop waiting for overlapped event. For example, you can wait for overlapped event with timeout. After timeout expired and no data is read, you can do everything you need, file handle is valid. – Alex F Oct 13 '10 at 07:35
  • Imagine I am reading/writing to sockets. Thread 1 doesn't write anything but tries reading with a timeout. Once the timeout occurs, Thread 2 writes to the socket and tries reading the response. If I never cancel Thread 1's operation, it'll "steal" bytes that should go to Thread 2. – Gili Oct 13 '10 at 22:15
  • Cancelling overlapped operation is done by setting appropriate event. As you can see from my sample, after executing ReadFile (which returns immediately) reading thread waits on WiatForMultipleObjects for overlapped event and any other events you need. By setting some additional event, you can stop this waiting, which is equal to cancelling read operation. – Alex F Oct 14 '10 at 06:07
  • Just because WaitForMultipleObjects() returns doesn't mean the underlying file-system stops reading. Thread 1 will still "steal" bytes from Thread 2. – Gili Oct 14 '10 at 06:51
  • @Gili: In case of file system (or any seekable stream), no it won't steal anything. Thread 2 will get the bytes at the offset it requested. – Ben Voigt Oct 15 '10 at 15:27
  • @Ben, I should have mentioned: I'm reading from a serial port. – Gili Oct 16 '10 at 03:33
  • 2
    -1, this is plain wrong. You can't let an overlapped operation dangle this way. Once ReadFile finishes, it will write data into the buffer (which may not even exist anymore) and update the overlapped structure (which, again, may no longer exist). You must always wait for the operation to finish -- calling `CancelIo` will allow the operation to finish prematurely. – avakar Dec 06 '11 at 15:42
  • @avakar - so, is it enough to call CancelIo in by sample before break to make this working correctly? – Alex F Dec 07 '11 at 06:45
  • 1
    @AlexFarber, almost, after calling `CancelIo`, you still need to wait for the operation to finish. If the operation gets cancelled (and it doesn't have to, since it can either finish before you call `CancelIo`, or the driver responsible for the file need not support cancellation), `GetOverlappedResult` will return `ERROR_CANCELLED`. – avakar Dec 07 '11 at 08:02
-1

If you're already using overlapped operations, why do you need to cancel I/O at all? The entire concept of 'cancelling' an in-flight I/O operation is really race-prone, and totally subject to the underlying device stack you're trying to write to; really the only time you'd want to do this is to unblock another thread who is waiting on the completion of that I/O.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • 1
    Imagine I am reading/writing to sockets. Thread 1 doesn't write anything but tries reading with a timeout. Once the timeout occurs, Thread 2 writes to the socket and tries reading the response. If I never cancel Thread 1's operation, it'll "steal" bytes that should go to Thread 2. – Gili Oct 13 '10 at 22:16
  • Who's waiting, it's an async operation - just have a way for the completion routine to ignore the result if you don't want it. I guess I see your point about in-flight unwanted I/Os getting in the way of future I/Os, but I still don't think that's a huge concern (Edit: Whoops, the above comment changed while I wrote my response) – Ana Betts Oct 13 '10 at 22:18
  • @Gili Hmmm, I think I see what you're saying now. Though I'd argue that designing your app to have thread affinity like that is a bad idea - it's better to assume completion callbacks come back on an *arbitrary* thread, then dispatch the result to the right place. You have a pretty motivating example though in general. – Ana Betts Oct 13 '10 at 22:25
  • Furthermore, consider the fact that the buffer passed into ReadFile() must remain valid for the duration of the read operation. I am providing an API on top of ReadFile(). This means that from the end-user's point of view, he needs to keep the buffer valid outside the scope of a read operation (i.e. he thinks the read is over when he cancels it). It's not clear to him when it is safe for him to reclaim the buffer. – Gili Oct 13 '10 at 22:26
  • The way that kernel drivers handle this is by state machines, you have the "Waiting for response X" state, then you handle and interpret whatever response you get back regardless of what thread you're in. – Ana Betts Oct 13 '10 at 22:27
  • @Gili: Your socket protocol is broken, not just in the presence of CancelIo. Even if CancelIo worked according to whatever behavior you preferred, Thread 2 could still read the block that Thread 1 was looking for, because it could have been delayed by TCP retransmits, etc. – Ben Voigt Oct 15 '10 at 15:30
  • @Ben, I guess I gave a bad example. I am reading from a serial port. – Gili Oct 16 '10 at 03:38