In general, if there's no event, the file handle itself is used as an "event". That is, it's designaled when an async I/O begins [queue_async()], and signaled when it completes [async_set_result()]. I don't remember if the handle is supposed to be designaled for a synchronous call or one that doesn't return STATUS_PENDING.
I would expect however that if background data is sent behind the scenes, the file handle is not going to be designaled when we start to send that data, or signaled when it's done, so I would assert that the right thing to do is indeed to skip set_fd_signaled().
- If there is racing send(). It may actually interpose the data in between the short written part and the queued one. That will be wrong, although not worse what is happening now: if now short read happens, the racing data may be interposed the same way, regardless of whether the app is going to handle short write or not. This may be fixed with some added synchronization but so far seems unnecessary;
We indeed can't protect against races with another I/O. Actually protecting against that would be hard; we'd either need to always copy (I think this can be dismissed as untenable) or extend the async infrastructure to allow an async to be "completed" but actually still processing.
This honestly may be another good argument that async is two different things awkwardly rolled into one (namely, a way to coordinate client and server I/O, and the server's representation of an IRP).
Alternatively we just ignore it for now.