A pending synchronous read (for example on a CreatePipe byte stream) hands off to the server and then calls wait_async() on its issuing thread. If the completion APC arrives in that gap, queue_apc cannot signal the busy issuer on iOS and ios_apc_requeue_target looks for another thread of the process already waiting in the server. With none, the APC was dropped: thread_apc_destroy() then completed the async with the APC's STATUS_ALERTED and zero bytes without running the callback that copies the reply, so the blocking read returned without its data.
When the APC belongs to a pending blocking async with a live wait handle issued by that same thread, keep it queued on the issuer: its next wait is the server wait_async() that processes system APCs. Overlapped I/O and every other APC keep the existing requeue/drop behaviour.
Found while analysing a Steam client IPC failure in a Unity game (CClientPipe::BWriteAndReadResult: BReadResult failed after a 5-byte header+first-byte read). Reasoned from the code path and that log; not yet confirmed on device to remove that failure.
Test status: reasoned from code and a device log; not yet confirmed on device to remove the Steam IPC failure. No dependencies.