Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

391–400 of 499 posts

Re: Why asynchronous Rust doesn't work

#391
The Clojure dialect of Lisp comes with a software transactional memory (STM). This basically provides relational database atomicity semantics around in-memory variable access. 99% of the pain of concurrent programming goes away.

At a high level, you provide a pure function that takes the current value. The system re-runs it as many times as needed to sort out collisions with other threads, then stores the new value -- which then becomes the argument for the pure update functions running in all the other threads.

Has anyone considered an STM for Rust?

Re: Why asynchronous Rust doesn't work

#392
post #184

Earlier quoted context omitted.

` fn do_work_and_then(func: fn(i32)) { thread::spawn(move || { // Figuring out the meaning of life... thread::sleep_ms(1000); // gee, this takes time to do... // ah, that's it! let result: i32 = 42; // let's call the `func` and tell it the good news... func(result) }); } ` you missed a snippet, a thread is spawned. edit: formatting

If for some magic reason thread::sleep_ms(1000) takes longer than 2000ms the main function would reach its end and deallocate the closure that is about to get called. Basically use after free.

That is technically a race (sure) but at that point the program is getting killed anyway. Agree on the overall point though. I am a big fan of moving complete ownership / lifetime. It just makes things easier to reason about. In Chromium weak pointers are used to solve this use after free problem.

Re: Why asynchronous Rust doesn't work

#393
post #372

Earlier quoted context omitted.

“Rust would have been a better language by breaking its stability guarantees” is just saying “Rust would have been a better language by not being Rust.” Maybe true, but not relevant to the people whose work you’ve blanket criticized. Rust language designers have to work within the existing language and your arguments are in bad faith if you say “async could have been perfect with all this hindsight and a few breaking…

I do not think that impossibility of a reliable async Drop in Rust 1 is a proven thing (prior to the stabilization of async in the current form). Yes, it may require some unpleasant additions such as making Futures and async fns more special than they are right now and implementing it with high probability would have required a lot of work (at least on the same scale as was invested into the poll-based model), but it…

I don’t agree with this analysis TBH - async drop has been revisited multiple times recently with no luck. Without a clear path there I don’t know why that would seem like an option for async/await two years ago. Do you actually think the language team should have completely exhausted that option in order to try to require an allocator for async/await?

Async drop would still not address the single-allocation-per-state-machine advantage of the current design that you’ve mostly not engaged with in this thread.

Re: Why asynchronous Rust doesn't work

#394

Earlier quoted context omitted.

I meant the callback based approach described in the article, for example take this line from it: >Unfortunately, this approach nevertheless forces allocation at almost every point of future composition, and often imposes dynamic dispatch, despite our best efforts to avoid such overhead. It clearly does not apply to the model which I've described earlier. Of course, the described FSM state transition functions can be…

> Of course, the described FSM state transition functions can be rightfully called callbacks, which adds a certain amount of confusion. No, I'm not talking about the state transition functions. I'm talking about the runtime - the thing that will call the state transition function. In the current design, abstractly, the runtime polls/checks every if future if it's in a runnable state, and if so executes it. In an comp…

>In an completion based design the future itself tells the runtime that the value is ready

I think there is a misunderstanding. In a completion-based model (read io-uring, but I think IOCP behaves similarly, though I am less familiar with it) it's a runtime who "notifies" tasks about completed IO requests. In io-uring you have two queues represented by ring buffers shared with OS. You add submission queue entries (SQE) to the first buffer which describe what you want for OS to do, OS reads them, performs the requested job, and places completion queue events (CQEs) for completed requests into the second buffer.

So in this model a task (Future in your terminology) registers SQE (the registration process may be proxied via user-space runtime) and suspends itself. Let's assume for simplicity that only one SQE was registered for the task. After OS sends CQE for the request, runtime finds a correct state transition function (via meta-information embedded into SQE, which gets mirrored to the relevant CQE) and simply executes it, the requested data (if it was a read) will be already filled into a buffer which is part of the FSM state, so no need for additional syscalls or interactions with the runtime to read this data!

If you are familiar with embedded development, then it should sound quite familiar, since it's roughly how hardware interrupts work as well! You register a job (e.g. DMA transfer), dedicated hardware block does it, and notifies a registered callback after the job was done. Of course, it's quite an oversimplification, but fundamental similarity is there.

>I think your design ultimately requires futures to be implemented as a language feature, rather than a library

I am not sure if this design would have had a Future type at all, but you are right, the advocated approach requires a deeper integration with the language compared to the stabilized solution. Though I disagree with the opinion that it would've been impossible to do in Rust 1.

Re: Why asynchronous Rust doesn't work

#395
post #162

What’s wrong with async_std? The author calls it spicy. I’ve been using Surf and Tide which are built atop it and things have been a breeze. It is essentially a standard lib where all functions are red (a solution as I’d interpret things based on the linked rant). In my experience rust async’s only wart is the incredibly confusing function signatures you get with Box<Pin<... which I would think can be cleaned up.

> What’s wrong with async_std?

The problem is that it exists. Or rather, that multiple async libraries exist and every attempt to employ asynchronous programming in Rust inevitably leaves you with the problem of aligning these different, redundant, incompatible, incomplete, evolving async libraries, which is an unmitigated disaster.

Rust, all by itself, is a non-trivial hill climb for developers. They're willing to bust out the climbing tools and make the attempt because they see the value. But then they get hit in the head by debris like futures::io::AsyncRead tokio::io::AsyncRead and that is it; they come down off the hill and go find something else to scale. Worse yet they don't talk about it; they love Rust, the idea of Rust, and they want it to thrive so they avoid being critical.

IO and async is fundamental. It's too fundamental to suffer multiple, partial, incompatible, ever evolving realizations. This is the number one issue that threatens Rust. People have only so much patience to spare; only so many attempts they're will to make climbing the same hill.

I have hope. Companies like Amazon and Microsoft are making serious commitments to Rust because they too see the value. They're going to get knocked in the head by the same falling debris. And so there is a chance that while this is all still young and the MBAs and lawyers and rent seekers aren't yet savvy, Rust will receive the sort of tedious, expensive labor it so desperately needs to mature.

Re: Why asynchronous Rust doesn't work

#396

The Clojure dialect of Lisp comes with a software transactional memory (STM). This basically provides relational database atomicity semantics around in-memory variable access. 99% of the pain of concurrent programming goes away. At a high level, you provide a pure function that takes the current value. The system re-runs it as many times as needed to sort out collisions with other threads, then stores the new value -…

There have been some vague discussions, but STM is (as you mention), tough in an impure language. It is also not clear that it achieves Rust's efficiency goals.

Re: Why asynchronous Rust doesn't work

#397
post #172

Earlier quoted context omitted.

I wasn't aware of the F# heritage, that's interesting. I'm curious why the scala proposal wouldn't cite it. Especially surprising since scala's at least superficially looks more like F#'s than it does like C#'s. I don't dispute (as I have had to say repeatedly in other branches of this) that the roots of futures as a concept are in functional programming, but the path I'm saying I see here is effectively: - haskell/m…

I don't see async/await (at least when built on top of futures/promises) as a procedural thing - the parts of C# where it's used are the least procedural parts of C#, and Python has always been multi-paradigm. I'd say it's mainly a way of doing monadic futures in languages that don't have monads (mainly because of lacking HKT) - hence why F# adopted it first, and then it made its way into functional-friendly language…

Sure. What I'm getting at is that I see the specific syntax of "async-await" as a synthesis of concepts from functional and procedural heritage. I agree about it being a spectrum and all that, so I think we're mostly just talking past each other about the specifics of how it came to be and using different ways of describing that process.

Re: Why asynchronous Rust doesn't work

#398

Of course a fiber/green threads based solution would be easier to use, but Rust is supposed to provide zero cost abstractions for concurrency: Maximum performance and efficiency (and safety) coming first, usability second. THat is perfectly fine. Whereas others (Go for example) sacrifice some efficiency and performance to ergonomics.

Is it still zero cost if the price to pay is a steep increase of code complexity? The current model also still requires a lot of boxing and cloning, so it is far from being “zero cost” as advertised.

> Is it still zero cost if the price to pay is a steep increase of code complexity?

Yes, because the "cost" in "zero-cost" only refers to runtime cost.

Re: Why asynchronous Rust doesn't work

#399
post #393

Earlier quoted context omitted.

I do not think that impossibility of a reliable async Drop in Rust 1 is a proven thing (prior to the stabilization of async in the current form). Yes, it may require some unpleasant additions such as making Futures and async fns more special than they are right now and implementing it with high probability would have required a lot of work (at least on the same scale as was invested into the poll-based model), but it…

I don’t agree with this analysis TBH - async drop has been revisited multiple times recently with no luck. Without a clear path there I don’t know why that would seem like an option for async/await two years ago. Do you actually think the language team should have completely exhausted that option in order to try to require an allocator for async/await? Async drop would still not address the single-allocation-per-stat…

>I don’t agree with this analysis TBH

No worries, I like when someone disagrees with me and argues his or her position well, since it's a chance for me to learn.

>async drop has been revisited multiple times recently with no luck

The key word is "recently", meaning "after the stabilization". It's exactly my point: this problem was not sufficiently explored in my opinion prior stabilization. I would've been fine with a well argued position "async Drop is impossible without breaking language changes, so we will not care about it", but now we try to shoehorn async Drop on top of the stabilized feature.

>Async drop would still not address the single-allocation-per-state-machine advantage of the current design that you’ve mostly not engaged with in this thread.

I don't think you are correct here, please see this comment: https://news.ycombinator.com/item?id=26408524

Re: Why asynchronous Rust doesn't work

#400
>Before, the function we passed to do_work_and_then was pure: it didn’t have any associated data, so you could just pass it around as a function pointer (fn(i32)) and all was grand. However, this new function in that last example is a closure

Those are different things! Anything pure (i.e. returns same output) could still require a closure if it captures heap-allocated data - which in Rust would entail free-ing and transferring ownership and all that.

Post reply on HN