If you’re waiting for I/O to complete, it helps if you actually have an I/O to begin with


We saw earlier the importance of waiting for I/O to complete before freeing the data structures associated with that I/O. On the other hand, before you start waiting, you have to make sure that you have something to wait for.

A customer reported a hang in Get­Overlapped­Result waiting for an I/O to cancel, and the I/O team was brought in to investigate. They looked at the I/O stack and found that the I/O the customer was waiting for was no longer active. The I/O people considered a few possibilities.

  • The I/O was active at one point, but when it completed, a driver bug prevented the completion event from being signaled.
  • The I/O was active at one point, and the I/O completed, but the program inadvertently called Reset­Event on the handle, negating the Set­Event performed by the I/O subsystem.
  • The I/O was never active in the first place.

These possibilities are in increasing order of likelihood (and, perhaps not coincidentally, decreasing order of relevance to the I/O team).

A closer investigation of the customer's code showed a code path in which the Read­File call was bypassed. When the bypass code path rejoined the mainline code path, the code continued its work for a while, and then if it decided that it was tired of waiting for the read to complete, it performed a Cancel­Io followed by a Get­Overlapped­Result to wait for the cancellation to complete.

If you never issue the I/O, then a wait for the I/O to complete will wait forever, since you're waiting for something that will never happen.

Okay, so maybe this was a dope-slap type of bug. But here's something perhaps a little less self-evident:

// there is a flaw in this code - see discussion
// assume operating on a FILE_FLAG_OVERLAPPED file
if (ReadFile(h, ..., &overlapped)) {
 // I/O completed synchronously, as we learned earlier
} else {
 // I/O under way
 ... do stuff ...
 // okay, let's wait for that I/O
 GetOverlappedResult(h, &overlapped, &dwRead, TRUE);
 ...
}

The Get­Overlapped­Result call can hang here because the comment "I/O is under way" is overly optimistic: The I/O may never even have gotten started. If it never started, then it will never complete either. You cannot assume that a FALSE return from Read­File implies that the I/O is under way. You also have to check that Get­Last­Error() returns ERROR_IO_PENDING. Otherwise, the I/O failed to start, and you shouldn't wait for it.

// assume operating on a FILE_FLAG_OVERLAPPED file
if (ReadFile(h, ..., &overlapped)) {
 // I/O completed synchronously, as we learned earlier
} else if (GetLastError() == ERROR_IO_PENDING) {
 // I/O under way
 ... do stuff ...
 // okay, let's wait for that I/O
 GetOverlappedResult(h, &overlapped, &dwRead, TRUE);
 ...
} else {
 // I/O failed - don't wait because there's nothing to wait for!
}
Comments (12)
  1. Alex Grigoriev says:

    You mean "the call to ReadFile was bypassed" not "the call to ReadFileEx". GetOverlappedResult doesn't work in pair with ReadFileEx at all.

    [Fixed, thanks. -Raymond]
  2. Joshua says:

    Nice one! Normally its a good idea to check your assumptions. This customer didn't.

  3. blah says:

    Musta been a junior coder. How could a read operation ever fail?

  4. davep says:

    "Nice one! Normally its a good idea to check your assumptions. This customer didn't."

    Well, at least they commented about their assumptions. (The comment was a help in figuring out the problem.)

  5. Tihiy says:

    Is there any benefit of issue-async-I/O and wait-in-the-same-thread over just-perform-sync-I/O?

  6. Crescens2k says:

    Tihiy:

    The samples where they are waiting in the same thread is often just a simplification. In real world apps, you would use completion ports or async io and then do other things while checking occasionally, or put the io onto a seperate thread. Samples are always meant to illustrate the issue, not provide a real life usage since real life usage is much more complex and would distract people from what they are meant to be looking at. But as you noted, there is no benefit to start an async operation and wait.

  7. S says:

    Totally off-topic but here is a youtube video showing someone upgrading from MS-DOS 5.0 through every version of Windows up to Windows 7. Applications that are twenty years old still work! http://www.youtube.com/watch

  8. Gabe says:

    There are a few common scenarios for issuing an async I/O and waiting in the same thread. The most obvious is when you want to time out or otherwise cancel the operation. Prior to Vista there was no way to cancel a synchronous I/O, so if you wanted to cancel an I/O (because the user wanted to cancel or the operation timed out) you had to make it async.

    The other common reason is that you have to do some I/O and a computation. You start the I/O, perform your computation, then wait for the I/O to complete so that you can continue with the result of the I/O.

  9. Koro says:

    "Is there any benefit of issue-async-I/O and wait-in-the-same-thread over just-perform-sync-I/O?"

    Well for starters you are not limited to waiting *only* on that, you can wait on multiple operations at once, or wait for some cancellation event as well.

  10. Neil says:

    Surely it must be a source of confusion to use ERROR_IO_PENDING to indicate that the I/O was successfully initiated?

  11. dave says:

    Surely it must be a source of confusion to use ERROR_IO_PENDING to

    indicate that the I/O was successfully initiated?

    Perhaps, but it's less clumsy than having two outputs, one to say whether the call was successful and another to say whether the I/O had never been started, had finished, or was ongoing.

    The real trouble is that Win32 has only one way to say 'success'.  In the I/O system, 'pending' is not an error but an alternative success status.

    Well, that and the naming convention that gives us 'ERROR_SUCCESS' ;-)

  12. Gabe says:

    The title, for English majors: "Before you start waiting for Godot, make sure Godot is actually on his way."

Comments are closed.

Skip to main content