The server is made aware of async completion after the IOSB (and output buffers, if applicable) are filled.
I originally started without the new server call (see e.g. the direct `async_complete_cancel()` call still in `set_async_direct_result()`), then I think I didn't find out all the cases so I added the new server call.
or by closing the APC_ASYNC_IO thread APC handle by returning back to select (asynchronous completion).
Probably I missed this one at least.
This is necessary because completion has to be signaled (event, IOCP, user APC) after the IOSB and buffers are filled. All of this is done in async_set_result(); cancellation can also be handled there.
I'll move the `async_complete_cancel()` call there and see what happens. Thanks a lot!
It's probably worth also noting, as I look at the code, that checking for STATUS_CANCELLED is neither sufficient (cancelled asyncs can still continue as normal for various reasons) nor necessary (STATUS_CANCELLED can be returned for asyncs that are not formally cancelled with NtCancelIo etc.)
Right, I think I should just get rid of the check and call `async_complete_cancel()` unconditionally.