Open nrc opened 2 years ago
Some thoughts from initial investigations:
The key difference between non_blocking_read
and async read
is about intentions and invariants: if non_blocking_read
would block then that is probably due to the data already being consumed, so the caller should go back to waiting for ready
. OTOH, async read
might be not ready for many reasons, and the expected behaviour is for the caller to continue to poll the returned future.
If we only have async read
, then there is the problem that inside the function body, we don't know whether we have been called from a poll loop (e.g., awaiting the method) or an explicit ready loop. That means we cannot write an optimal implementation because there must be a ready loop inside the method (which would cause problems if called from an external ready loop).
Implementers must also only use a single await and not return pending for any reason other than to indicate that the call would block. If they don't do this, then calling from a ready loop will not work because on a pending result, the caller will drop the future and await ready
again. I think that means that code in ready
must be duplicated in async read
.
Another issue is that although this simplifies the API of Read and Write, it doesn't make things very easy for intermediate implementers. They must still consider ready
and cannot write async Read
as a simple wrapper function.
What about completion based async io like io-uring?
In io-uring setting, you can submit a read I/O to network or filesystem in a truly async manner and simply wait for the entry to appear in cqe, and you don't need any poll
/epoll
or other ready
call.
Completion based IO is likely to use BufRead or OwnedRead or similar, rather than these functions
I'm in agreement with @nrc here.
If we were to add a Leak
auto trait, then completion-based I/O systems could use async fn read
as well as a BufRead
interface (for io_uring
’s registered buffer mode). I think that’s the optimal solution since it has parallels with synchronous code (enabling keyword genericity with async) and allows completion and readiness-based systems to operate under the same API (so runtimes can transparently choose between the two at runtime). Therefore, by having the async I/O traits mirror their synchronous counterparts, although today it won’t benefit completion-based systems, it might be able to in future and I think that’s a very important feature to have.
With uring, it's interesting because you actually have efficient readiness-based IO via IORING_OP_POLL, however you just have more efficient APIs that are completion based which you generally want to use instead.
Because returning
WouldBlock
is pretty much like returningPending
, except that it gets processed by the user in the ready loop rather than an executor, but that seems not important. If it works, this would make the Read and Write traits much simplerCredit for the idea to @joshtriplett