On Tue Jul 22 23:34:30 2025 +0000, Matteo Bruni wrote:
It does not. The async is set to `signaled` right away, so that
Status can be set to STATUS_PENDING and subsequently checked via `GetOverlappedResult()`. But I see the point. I'll look for some way to do it without "unsignaling" the async.
If that's the case this is also a problem for sync IO which can in
theory run away from wait before IOSB status is set? Maybe it is hard to get in practice (or even impossible before ntsync) because system APC with IO completion is delivered to waiting thread. But I think we want the same for sync IO wait as well, async should only be signaled once IOSB is delivered. There is no reason this thing should work different for cancellation wait, right? IIRC for synchronous IO it depends on a number of factors but I think it can happen in theory. Looking at `async_handoff`, it looks like device reads (which have `unknown_status` set) will always take the async path, server-side. The other side is that actually async reads are sometimes effectively synchronous (plain file access generally does that, I think?) So we currently signal the async right away to communicate if we have a result right away (status != `STATUS_PENDING`) or not. That's my understanding at least. We could certainly change the async signaling semantics but we need to change all that depends on the current semantics as well.
The other thing to be careful about is to not "satisfy" the wait for
the final IOSB Status (i.e. we want the application to still be able to wait on the file handle or the OVERLAPPED event after `NtCancelIoFile()` returns).
Do we care when the wait completion for the IO caller happens WRT to
the waits in NtCancelIO? If this is async IO the caller should never meet async's wait object. I suppose it is not detectable whether NtCancelIO exited before wait on overlapped event or for IO completion or after, so do we care if IO completion is performed from async_wait been satisfied for NtCancelIO wait or before that when server processed cancellation? You're probably right. One less thing to worry about! :smile:
I probably haven't read the whole conversation completely, but I'm going to try to weigh in regardless:
* Asyncs are signaled when the client can return from the dispatcher function. That means:
- If the async is blocking, the async object is signaled only once the whole async is completed, and that includes filling the IOSB, if the IOSB needs to be filled—and note that in the case of synchronous failure it will *not* be filled.
- If the async is nonblocking, the async object is signaled once the initial status is known, but the async itself is not necessarily complete yet. Note also that the wait on the async returns with this initial status, which can be literally anything. This is a lightweight way of returning that information that doesn't need a whole system APC. There are two cases where the initial status is not known immediately:
* new-style async I/O that uses set_async_direct_result. This doesn't really matter because the client won't be waiting on the async handle until after it calls set_async_direct_result(), which will signal the async handle.
* device files (only once the IRP dispatcher returns). This is the only really important case.
This means that you *cannot* wait on an async to determine when it is cancelled. If it was an overlapped file handle to a device, the async handle will already be signalled. You could designal the async when cancelling it, but...
* I feel fairly strongly that async handles should not be designalled. I think everyone else in this thread has expressed the same.
Combined with the above, that means we *need* a new object to determine when an async is truly done.