!processirps shows no IRPs for my process but CancelIo still returns TRUE. And then GetOverlappedResult also returns TRUE with zero bytes transferred and OVERLAPPED::Internal equal to zero. I called it with bWait = TRUE.
Aside from an invalid handle, does CancelIo ever fail?
If the function succeeds, the return value is nonzero. The cancel operation for all pending I/O operations issued by the calling thread for the specified file handle was successfully requested.
I think that means that the case of no irps pending would also return nonzero. The failure case is where there is at least one pending irp that was not successfully canceled.
If you are trying to make an inference about what the ultimate status of outstanding IRP will be based on the return from CancelIo, you can't. Each IRP will report its own status and some may have completed before the call.
If you are asking if you can rely on CancelIo to cancel anything that is currently pending. That's a qualified yes. Qualified because by itself, you can't know if the call was actually made by the time that you called CancelIo - you need a lock. That's part of the reason why this API was quickly superseded and is not generally used. CloseHandle or CancelIoEx make a lot more sense in most designs
Thanks MBond2, My concern is blocking forever in GetOverlappedResult(bWait = TRUE) if there is nothing pending. I thought CancelIo would fail in that case but it succeeds. I guess as long as CancelIo succeeds I can be sure that GetOverlappedResult(bWait = TRUE) won't block forever (barring a bug in the driver).
If you are using an overlapped structure with an event this makes no sense. Either the related IO request indicated status ERROR_PENDING or it didn't, and you can only call GetOverlappedResult for the ERROR_PENDING case.
If you aren't using an event, if you are depending on the file handle, and you have multiple IO requests outstanding on that handle, there is no way to correlate the returned information with any specific request.
I'd suggest either using completion ports or one event per IO request.
Also, a simple counter (protected by a lock or interlocked operations,) is sufficient to know if there are outstanding IO requests.
GetOverlappedResult does not submit new IO. ReadFile, WriteFile or DeviceIoControl do. Those are the functions where the ambiguity happens re CancelIo. GetOveralppedResult only interprets what is set in the OVERLAPPED.
Originally OVERLAPED was meant to be opaque and subject to change, but since it has been stable for so long, and the design of the API means that binary compatibility means that it is almost impossible for MSFT to change it. They eventually documented it
For this discussion, only the hEvent member matters. If this is null, the the signaled state of the file handle itself will indicate completion. This is the least efficient way. Using a valid event handle, and a different one for each IO (or presumably multiple OVERLAPPED that are pending at any given time) allows differentiation between which completion has happened. IOCP (and the thread pool APIs that are built on top) provide the opportunity for MUCH more efficient designs, but may not be obvious.
In every case, you can depend on a valid sequence of calls to ReadFile etc., followed by GetOverlappedResult to never block forever. If you think that is happening, or that you think you need CancelIo to break out of that, then you probably have an error in your code
But I mentioned an ambiguity re CancelIo. If you make a call to ReadFile etc. and
the IRP is something that could take a long time, and a call to CancelIo, was the IRP in a cancellable state at the time that you tried to cancel it? From the UM side, there is no way to know that about a specific IRP - this is not a very useful API.
Oh I worded that wrong. I meant DeviceIoControl, then WFMO, one if which is OVERALPPED::hEvent, and another a shutdown event. If shutdown event signaled, then CancelIo. And if CancelIo succeeds, then a call to GetOverlappedResult.
My concern was if the usermode logic for some reason did not call DeviceIoControl, then upon receiving the shutdown event, there would be no pending I/O but the call to CancelIo succeeds nevertheless. I was worried in that case that GetOverlappedResult would block forever. But it does not. It succeeds and bytes transferred is zero.
You should not decide to call GetOverlappedResult based on the return code from CancelIo. If the result of the IO is important, then you should always make the call. If the intention is to abandon the IO, then abandon it and don't bother with the call at all
In the shutdown path, you should eventually call CloseHandle. You can call CancelIo in advance of that, but it usually doesn't matter much. avoiding edge cases with HANDLE value reuse being the notable exception; along with lengthy async cancel (rare)
If you call GetOverlappedResult using an OVERLAPPED structure that has never been used, then I'm not sure what happens. I wouldn't count on it no matter what it seems to do in testing. And surely there will be a difference between a zeroed struct and one with arbitrary values. A zeroed struct will block or not based on the signal state of the file HANDLE - which is hard to predict if no actual IO is being done. And arbitrary values could be either invalid or worse - a valid HANDLE that isn't signaled and won't be. That would block in a way that you are worried about
If you call GetOverlappedResult using an OVERLAPPED that represented a completed IO, you can call GetOverlappedResult an unlimited number of times on the same OVERLAPPED until you reuse / modify it (or the event HANDLE)
Completed IO could have been cancelled via CancelIo, or it could have completed normally. Neither CancelIo nor WFMO can't tell you which situation has happened, so you need to make decisions about what to do without relying on the return codes from those APIs
Thanks MBond2, I appreciate the detailed explanation. I had to zoom-out my thinking and realize the user-mode app should know if it has issued I/O for which it has not yet attempted to retrieve the result.