In the original CW bug (unfortunately private) there seem to be two contradictory claims about whether CancelIoEx() should wait or not. Should it? [Also, nobody seems to have ever checked CancelSynchronousIo()?]
`CancelIoEx()` should wait, the tests in 6/8 should cover that. Those don't check `CancelSynchronousIo()`, although I had the impression that there was something in the preexisting tests implying that it does not wait? I'll have another look.
Well, kind of, but there's some subtlety. In a sense, I think the tests aren't exactly convincing, because I think the entire cancellation process is more asynchronous on Wine than it is on Windows.
* I suspect that NtCancelIo() and variants synchronously call the cancellation routine, even if they don't wait for the IRPs in question to complete. This is probably testable, although it may get close to unnecessarily poking at internals.
* The cancellation routine "must" [1] complete the IRP with STATUS_CANCELLED. The language doesn't *say* "synchronously", but I think it's arguably implied. I haven't tested what happens if you don't do this—maybe it works, or maybe it trips up an assertion in the kernel, or maybe it causes more subtle bugs. On the other hand, if you don't *have* a cancellation routine, there's nothing to cancel. In any case I suspect that all of the native drivers cancel synchronously, *if* their IRPs are cancellable.
* I suspect that IoCompleteRequest() will in fact fill the IOSB (and user output buffers, if not direct) and signal everything synchronously. This is probably not possible to prove (how can you be sure that it's synchronous vs a very fast kernel APC?)
The reason Wine doesn't already do this synchronously is because it can't deliver the IOSB and user output buffers synchronously, and waiting for the async to be complete is difficult with its current asynchronous structure (as evidenced by, well, the amount of changes this merge request requires). Not to mention winedevice IRPs.
If all this is correct, the only real difference there *can* be on Windows is whether NtCancelIo() etc. wait for non-cancellable I/O. The other test performed in the CW bug—that suggested that CancelIo() waits but CancelIoEx() doesn't—was the rather interesting approach of trying NtWriteFile() with enormous buffers, thus limiting oneself by disk space. IIRC disk I/O is not cancellable.
Of course, if CancelIoEx() *effectively* waits for the cancel routine, doing it the way we do here may be the right approach anyway. You could probably also find a way to make some async_cancels wait for only those asyncs actually terminated by the cancel routine, although it gets tricky when the cancel routine itself will be asynchronous for winedevice IRPs.
[1] https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nc-wdm-dr...