6 ms·
>There's a difference between "we decided this 3 years ago" and "we rushed the decision". As far as I understand the situation, the completion-based API simply
by newpavlov 6y ago
>There's a difference between "we decided this 3 years ago" and "we rushed the decision".
As far as I understand the situation, the completion-based API simply was not on the table 3 years ago. io-uring was not a thing and there was a negligible interest in properly supporting IOCP. So when a viable alternative has appeared right before stabilization of the developed epoll-centric API, the 3 year old decision has not been properly reviewed in the light of the changed environment and instead the team has pushed forward with the stabilization.
>because changing the model would cause a huge amount of ecosystem churn.
No, the discussion has happened before the stabilization (it's literally in the stabilization issue). Most of the ecosystem at the time was on futures 0.2.
Regarding your examples, I think you simply look at the problem from a wrong angle. In my opinion compiler should not desugar async fns into usual functions, instead it should construct explicit FSMs out of them. So no need for Arcs, the String would be stored directly in the "output" FSM state generated for foo. Yes, this approach is harder for compiler, but it opens some optimization capabilities, e.g. regarding the trade-off between FSM "stack" size and and number of copies which state transition functions have to do. AFAIK right now Rust uses "dumb" enums, which can be quite sub-optimal, i.e. they always minimize the "stack" size at the expense of additional data copies and they do not reorder fields in the enum variants to minimize copies.
In your example with two sub-tasks a generated FSM could look like this (each item is a transition function):
1) initialization [0 -> init_state]: create requests A and B
2) request A is complete [init_state -> state_a]: if request B is complete do nothing, else mark that request A is complete and request cancellation of task B, but do not change layout of a buffer used by request B.
3) cancellation of B is complete [state_a -> state_c]: process data from A, perform data processing common for branches A and B, create request C. It's safe to overwrite memory behind buffer B in this handler.
4) request B is complete [init_state -> state_b]: if request A is complete do nothing, else mark that request B is complete and request cancellation of task A, but do not change layout of a buffer used by request A.
5) cancellation of A is complete [state_b -> state_c]: process data from A, perform data processing common for branches A and B, create request C. It's safe to overwrite memory behind buffer A in this handler.
(This FSM assumes that it's legal to request cancellation of a completed task)
Note that handlers 2 and 4 can not be called at the same time, since they are bound to the same ring and thus executed on the same thread. Other completion handler simply can not call another handler, since they are part of the same FSM and only one FSM transition function can be executed at a time. At the first glance all those states and transitions look like an unnecessary complexity, but I think that it's how a proper select should work under the hood.
- the_mitsuhiko 6y ago> As far as I understand the situation, the completion-based API simply was not on the table 3 years ago Completion APIs were always considered. They are just significantly harder for Rust to support.
- newpavlov 6y agoCan you provide any public sources for that? From that I've seen Rust async story was always developed primarily around epoll.
- withoutboats2 6y agoAlex Crichton started with a completion based Future struct in 2015. It was even (unstable) in std in 1.0.0: https://doc.rust-lang.org/1.0.0/std/sync/struct.Future.html https://doc.rust-lang.org/1.0.0/std/sync/struct.Future.html Our async IO model was based on the Linux industry standard (then and now) epoll, but that is not at all what drove the switch to a polling based model, and the polling based model presents no issues whatsoever with io-uring. You do not know what you are talking about.
- newpavlov 6y ago>Our async IO model was based on the Linux industry standard (then and now) epoll, but that is not at all what drove the switch to a polling based model Can you provide a link to a design document or at the very least to a discussion with motivation for this switch outside of the desire to be as compatible as possible with the "Linux industry standard"? >the polling based model presents no issues whatsoever with io-uring There are no issues with io-uring compatibility to such extent that you wrote about a whole blog post about those issues: https://boats.gitlab.io/blog/post/io-uring/ https://boats.gitlab.io/blog/post/io-uring/ IIUC the best solutions right now are either to copy data around (bye-bye zero-cost) or to use another Pin-like awkward hack with executor-based buffer management, instead of using simple and familiar buffers which are part of a future state.