March 3rd, 2011

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!
}
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.