nrc / portable-interoperable

Async fundamentals initiative: portable and interoperable
Apache License 2.0
75 stars 0 forks source link

async Read/Write: can we use async read instead of non_blocking_read? #10

Open nrc opened 2 years ago

nrc commented 2 years ago

Because returning WouldBlock is pretty much like returning Pending, 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 simpler

Credit for the idea to @joshtriplett

nrc commented 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.

NobodyXu commented 2 years ago

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.

nrc commented 2 years ago

Completion based IO is likely to use BufRead or OwnedRead or similar, rather than these functions

Noah-Kennedy commented 2 years ago

I'm in agreement with @nrc here.

SabrinaJewson commented 2 years ago

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.

Noah-Kennedy commented 2 years ago

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.