Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

131–140 of 499 posts

Re: Why asynchronous Rust doesn't work

#131
post #110

Earlier quoted context omitted.

I'm also curious about this. Boats wrote some about rust async and io-uring a while ago that's interesting[1], but also points out a very clear path forward that's not actually outside the framework of rust's Future or async implementation: using interfaces that treat the kernel as the owner of the buffers being read into/out of, and that seems in line with my expectations of what should work for this. But I haven't…

There's a workaround, but it's unidiomatic, requires more traits, and requires inefficient copying of data if you want to adapt from one to the other. However, I wouldn't call this a problem with a polling-based model. At least part of the goal here must be to avoid allocations and reference counting. If you don't care about that, then the design could have been to 'just' pass around atomically-reference-counted buff…

Wouldn't a more 'correct' implementation be moving the buffer into the thing that initiates the future (and thus, abstractly, into the future), rather than refcounting? At least with IOCP you aren't really supposed to even touch the memory region given to the completion port until it's signaled completion iirc.

Ie. to me, an implementation of read() that would work for a completion model could be basically:

    async read(&self, buf: T) -> Result
I recognize this doesn't resolve the early drop issues outlined, and it obviously does require copying to adapt it to the existing AsyncRead trait, or if you want to like.. update a buffer in an already allocated object. It's just what I would expect an api working against iocp to look like, and I feel like it avoids many of the issues you're talking about.

Re: Why asynchronous Rust doesn't work

#132
This is pretty overblown. I write async rust every day for my job, just fine, with no real problems. Probably because I'm consuming other libraries, I'm not trying to write my own. I use well-tested libraries like Actix-Web or occasionally Tokio. I've migrated multiple projects from futures to async/await once the syntax came out. '

The problems the author is describing might apply more to library authors, but for the many Rust programmers like me who are trying to solve problems, not write a framework, async is easy and practical.

Re: Why asynchronous Rust doesn't work

#133
post #75

Earlier quoted context omitted.

> Instead of carefully weighing advantages and disadvantages of both models, the decision was effectively made on the ground of "we want to ship async support as soon as possible" [1]. That is not an accurate summary of that comment. withoutboats may have been complaining about someone trying to revisit the decision made in 2015-2016, but as the comment itself points out, there were good reasons for that decision. Ma…

>That is not an accurate summary of that comment. How is to so, if he explicitly writes: > Suggestions that we should revisit our underlying futures model are suggestions that we should revert back to the state we were in 3 or 4 years ago, and start over from that point. Trying to provide answers to these questions would be off-topic for this thread; the point is that answering them, and proving the answers correct,…

> How is to so, if he explicitly writes:

There's a difference between "we decided this 3 years ago" and "we rushed the decision". At this point, it's no longer possible to weigh the two models on a neutral scale, because changing the model would cause a huge amount of ecosystem churn. But that doesn't mean they weren't properly weighed in the first place.

Regarding cyclicity… well, consider something like a task running two sub-tasks at the same time. That works out quite naturally in a polling-based model, but in a completion-based model you have to worry about things like 'what if both completion handlers are called at the same time', or even 'what if one of the completion handlers ends up calling the other one'.

Regarding dynamic allocations… well, what kind of desugaring are you thinking of? If you have

    async fn foo(input: u32) -> String;
then a simple desugaring could be

    fn foo(input: u32, completion: Arc);
but then the function has to responsible for allocating its own memory.

Sure, there are alternatives. We could do...

    struct Foo { /* state */ }
    impl Foo {
        fn call(self: Arc, input: u32, completion: Arc);
    }
Which by itself is no better; it still implies separate allocations. But then I suppose we could have an `ArcDerived` which acts like `Arc` but can point to a part of a larger allocation, so that `self` and `completion` could be parts of the same object.

However, in that case, how do you deal with borrowed arguments? You could rewrite them to Arc, I suppose. But if you must use Arc, performance-wise, ideally you want to be moving references around rather than actually bumping reference counts. You can usually do that if there's just `self` and `completion`, but not if there are a bunch of other Arcs.

Also, what if the implementation misbehaved and called `completion` without giving up the reference to `self`? That would imply that any further async calls by the caller could not use the same memory. It's possible to work around this, but I think it would start to make the interface relatively ugly, less ergonomic to implement manually.

Also, `ArcDerived` would have to consist of two pointers and there would have to be at least one `ArcDerived` in every nested future, bloating the future object. But really you don't want to mandate one particular implementation of Arc, so you need a vtable, but that means indirect calls and more space waste.

Most of those problems could be solved by making the interface unsafe and using something with more complex correctness requirements than Arc. But the fact that current async fns desugar to a safe interface is a significant upside. (...Even if the safety must be provided with a bunch of macros, thanks to Pin not being built into the language.)

Re: Why asynchronous Rust doesn't work

#134
post #129
post #30

I've been primarily coding in rust since 2018. I never cared for async/await, and I've never used it. (at some point, coding event loops became very natural/comfortable for me, and I have no trouble writing "manual" epoll code with mio/mio_httpc). one nice thing about rust's async/await is, you don't have to use it, and if you don't, you don't pay for it in any way. sure, I run into crates that expect me to bring in…

I don't know how anybody can say this with a straight face. Even in a systems context I think it's pretty reasonable to want to either perform or receive a HTTP request, as soon as you do that in Rust you are funneled into Hyper or something built on top of it (like reqwest) and instantly are dependent on tokio/mio. The very first example in the reqwest readme^1 has tokio attributes, async functions AND trait objects…

jstrong didn't mention anything about beginners or how a typical user is nudged. It's just a description of how they work. They even pointed out which HTTP library they use. There is nothing in their post that requires a curved face.

Re: Why asynchronous Rust doesn't work

#135

Earlier quoted context omitted.

I'm not clear on if this is supposed to be disagreement or elaboration or education. The fact that in a language like Haskell, you can perform something like async-await with futures (which are absolutely a kind of monad) in a natural way is precisely what I had in mind with what you quoted. Regardless, the specific heritage of async-await syntax seems rooted in procedural languages (that do borrow much else as well…

You don't need async/await to do monadic comprehension in Scala, it's built into the language from the very beginning with `for`. This was inspired by do notation, which came about ~1998.

[deleted]

Re: Why asynchronous Rust doesn't work

#136
post #85
post #76

Earlier quoted context omitted.

I really don't see a problem with function color in a typed language. Every function has color and compiler enforces you pass right color. Async is just another type that may have some convenient syntax. It might be inconvenient in untyped languages like js since you couldn't compose them.

It's a problem in that you can't arbitrarily compose synchronous and asynchronous functions. Many languages have a distinction between statements and expressions, and some language designers thought that was ugly and designed languages where everything is an expression. But because of how people think, an application typically has a rough hierarchy to it by the designer's intent. So the fact that this becomes a bit m…

You can't arbitrarily compose 2 functions with mismatched types. So i don't think this argument holds in a typed language.

Re: Why asynchronous Rust doesn't work

#137

Again, feeling old, I'm not seeing a lot wrong with threads. A thread pool, if you insist, but explicit workflows as opposed to chaining together callbacks and contexts ... just seems really easy.

I think you're spot on. For 99% of the use cases, traditional threads are the way to go. It's only when you have a truly massive number of logical threads (>100k, or even >1M) not doing anything most of the time, that OS threads reach their limits due to their aggregate stack size. In other words: web services, where each server thread either waits for input from the client or for a database reply, and only if they have to handle hundreds of thousands of concurrent clients.

This is not a concern for most Rust users, but it is a concern for large companies that are potential patrons of an up-and-coming programming language. I'm not saying it's wrong to cater to their needs, rather that it's not something over which regular Rust users should lose sleep.

Re: Why asynchronous Rust doesn't work

#138
post #83
post #76

Earlier quoted context omitted.

I really don't see a problem with function color in a typed language. Every function has color and compiler enforces you pass right color. Async is just another type that may have some convenient syntax. It might be inconvenient in untyped languages like js since you couldn't compose them.

It’s not a problem of being error-prone, the problem is that the ecosystem is split. As a crate author, do I implement my functions for color A or color B? Every crate author will eventually encounter this question and the inevitable fall out from making the wrong decision. Presently, the only way to deal with this is implement your functionality in both colors.

If your function represents async workflow then you can just implement the async side and the caller can just block on it if they want that semantics. The problem with the ecosystem in Rust is there are various runtime libraries and no standard way to factor it out. You shouldn't need to implement the blocking semantics since it should be trivial.

Re: Why asynchronous Rust doesn't work

#139
post #67

A bigger problem in my opinion is that Rust has chosen to follow the poll-based model (you can say that it was effectively designed around epoll), while the completion-based one (e.g. io-uring and IOCP) with high probability will be the way of doing async in future (especially in the light of Spectre and Meltdown). Instead of carefully weighing advantages and disadvantages of both models, the decision was effectively…

FWIW I've done a fair bit of researching with io_uring. For file operations it's fast, bit over epoll the speedups are negligible. The creator is a good guy but they're having issues with the performance numbers being skewed due to various deficiencies in the benchmark code, such as skipping error checks in the past. Also, io_uring can certainly be used via polling. Once the shared rings are set up, no syscalls are n…

We've briefly been playing with io_uring (in async rust) for a network service that is CPU-bound and seems to be bottlenecked in context switches. In a very synthetic comparison, the io_uring version seemed very promising (as in "it may be worth rewriting a production service targeting an experimental io setup"), we ran out of the allocated time before we got to something closer to a real-world benchmark but I'm fairly optimistic that even for non-file operations there are real performance gains in io_uring for us.

I'm not sure io_uring polling counts as polling since you're really just polling for completions, you still have all the completion-based-IO things like the in-flight operations essentially owning their buffers.

Re: Why asynchronous Rust doesn't work

#140
post #128
post #126

Earlier quoted context omitted.

If function literals don't have access to the usual idioms of the language - which, in the case of Rust, means state - then functions are not first-class. > It's true that when you have a closure with state then Rust forces you to reason about the ownership of that state, but that's par for the course in Rust. The problem isn't that you have to reason about the state ownership, it's that you can't abstract over it pr…

You can't abstract over it in the same way that you can in Haskell, because you have to manage ownership. You can't abstract away ownership as easily, because the language is designed to make you care about ownership. I write Rust code for my day job, and I frequently use map/reduce/filter. IMO if I can write all my collection-processing code using primitives like that, it's got first-class functions.

It's not about "abstracting away" ownership, it's about being polymorphic over it. I want to keep the distinction between Fn, FnOnce, and FnMut. But I want to be able to write a `compose` function that works on all three, returning the correct type in each case. That's not taking away from my ability to manage ownership, it's letting me abstract over it.
Post reply on HN