Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

91–100 of 327 posts

Re: Local async executors and why they should be the default

#91

Async is and probably will always be less usable than blocking Rust. It is a very, very useful mode of operating when you really need two of its biggest benefits: lightweight cooperative concurrency and task cancellation, but it comes at a big usability cost. Rust software should use async tactically - in places where it is needed. Unfortunately handling http, which is a large part of many applications is actually a…

> lightweight cooperative concurrency

It's not cooperative if multiple things happen _at the same time_ which is always touted of rust async. Cooperative would be iterators, or generators, or coroutines/continuations. They let you do things in a single thread but have the execution order be mixed. That's concurrent, but not parallel. What you are talking about is parallel execution. That changes the classification away from cooperative. Sorry, just a pet peeve of mine.

Re: Local async executors and why they should be the default

#92
post #41

> If you know anything about asynchronous sockets it should be that multi-threading a socket doesn't actually yield you more requests / second, and it can actually lower it... Re-read this a few times, and I'm fairly convinced it is not generally true. The author is also being a bit confusing about what exactly he means by "socket" here. Because while it's true that multi-threading over a server socket (e.g. the one…

Having multiple threads calling 'accept' itself can be a win and Linux (and epoll) has explicit support for it.

Re: Local async executors and why they should be the default

#93
post #35

I find all the async stuff in Rust incredibly ugly, cumbersome, and its one of the biggest reasons I prefer C++, still. C++ lets me just write single- or multithreaded code, because none of the dependencies force their `async` stuff on me. Yeah, its up to me to ensure things are synchronized, but I'd rather do that than try to figure out how to get some dependency that isnt meant to use async to work in some async mo…

> but I'd rather do that than try to figure out how to get some dependency that isnt meant to use async to work in some async move closure tokio::spawn_blocking [1], see it's not that hard. But sure to use another language you must learn it, an it requires a bit if effort… [1] assuming you want to use tokio like post people do, but other executors should have the same kind of functions to do that as well if need be.

It's not always that simple. What if your sync lib has some callbacks, and you want to do something in the callbacks that requires async.

You could argue that this mismatch exists even with rust itself, where you have Drop, which is sync, and you might have to do something in your drop that requires async, like closing a network connection.

You have to pass in some runtime handle that you can use to spawn a task to do what you have to do. This is definitely not simple and beginner friendly. Or even worse - say goodbye to RAII and tell people that they have to explicitly call an async shutdown fn.

Re: Local async executors and why they should be the default

#94
post #21

> Yes the RwLock and mpsc comes from Tokio and lets you .await instead of blocking a thread, but these are not async primitives, these are multi-threading synchronization primitives. The only reason all this async stuff even exists is because we want concurrency. We want to say "while this one task waits for I/O, this other task will do stuff". So it's not too surprising to me that an intro to async would include syn…

Multi threading and concurrency are not the same. You can have a very high performance server that handles thousands of requests concurrently on a single thread. That's how node/deno do things. But the way to do things in async rust is that if you want concurrency you also have to use multithreading. At least that is what you see in all the examples and docs. As soon as you require your futures to be Send you have to…

I believe you can just annotate your tokio::main with flavor = "current_thread"

Re: Local async executors and why they should be the default

#95

Async is and probably will always be less usable than blocking Rust. It is a very, very useful mode of operating when you really need two of its biggest benefits: lightweight cooperative concurrency and task cancellation, but it comes at a big usability cost. Rust software should use async tactically - in places where it is needed. Unfortunately handling http, which is a large part of many applications is actually a…

> lightweight cooperative concurrency It's not cooperative if multiple things happen _at the same time_ which is always touted of rust async. Cooperative would be iterators, or generators, or coroutines/continuations. They let you do things in a single thread but have the execution order be mixed. That's concurrent, but not parallel. What you are talking about is parallel execution. That changes the classification aw…

If you use rust async with a local executor such as the tokio current thread runtime, it provides lightweight cooperative concurrency.

There is nothing ever happening at the same time, since you are on a single thread. That is why you don't need synchronization primitives such as Mutex but can live with something lightweight like RefCell. And that is why you can get by with non atomic reference counting smart pointers (Rc instead of Arc).

And it is cooperative in that you have to yield by calling await, otherwise nothing else will run.

What the article argues is that the option concurrent but not parallel, which both tokio and futures support in principle, should be advertised more and maybe even be the default.

Re: Local async executors and why they should be the default

#96
Just a personal take: after not coding with Rust for several months, I find it more and more difficult to return to an async code I was writing.

The whole thing just reads... ugly and inconsistent. It needs too much already-accumulated knowledge. As the article correctly points out, you need a bunch of stuff that are seemingly unrelated (and syntactically speaking you would never guess they belong together). And as other commenters pointed out, you need to scan a lot of docs -- many useful Tokio tools are not just not promoted, they are outright difficult to find at all.

Now don't get me wrong, I worked on projects where a single Rust k8s node was ingesting 150k events per second. I have seen and believed and I want to use Rust more. But the async story needs the team's undivided attention for a long time at this point, I feel.

Against my own philosophy and values I find myself attracted to Golang. It has a ton of ugly gotchas and many things are opaque... and I still find that more attractive than Rust. :(

This article is a sad reminder for me -- I am kind of drifting away from Rust. I might return and laugh at myself for this comment several months down the line... but at the moment it seems that my brain prefers stuff that's quicker to grok and experiment with. Not to mention writing demos and prototypes is objectively faster.

If I had executive power in the Rust leadership I'd definitely task them with taking a good hard look at the current state of async and start making backwards-incompatible changes (backed by new major semver versions of course). Much more macros or simply better-reading APIs might be a very good start. Start making and promoting higher-order concurrency and parallelism patterns i.e. the `scoped_pool` thingy for example.

Re: Local async executors and why they should be the default

#97
post #39

Earlier quoted context omitted.

I can see where you are coming from. But you can do most things purely in sync rust. There are sync rust multithreading libs like rayon that are a joy to use, and there are even blocking versions of popular http libraries like reqwest: https://docs.rs/reqwest/latest/reqwest/blocking/index.html They are usually doing some ugly stuff internally to make this work, but as a pure library user you don't have to care. If yo…

One thing I hate about "async" systems in general is the absurdity of suddenly having two types of functions behave differently with same syntax. And you have to add "await" keyword to async functions to make them synchoronous, i.e. behave normally. I find the Go's approach of "everything is synchronous, except when made asynchronous with the 'go' keyword" much more pleasant and much less error prone. Is there a Rust…

Go can do this because it’s closer to Java or C# than C. It has a runtime that gets compiled into every binary. Rust deliberately avoided that by design.

Re: Local async executors and why they should be the default

#98

Just a personal take: after not coding with Rust for several months, I find it more and more difficult to return to an async code I was writing. The whole thing just reads... ugly and inconsistent. It needs too much already-accumulated knowledge. As the article correctly points out, you need a bunch of stuff that are seemingly unrelated (and syntactically speaking you would never guess they belong together). And as o…

I fully agree that the async story needs attention.

Something as drastic as backwards-incompatible changes might not be needed.

But definitely much more documentation about best practices, highlighting the option of local async tasks, making the APIs for that more convenient, and also some low hanging fruits in terms of language syntax.

It seems a bit like while there was a lot of excitement about the async syntax a few years ago, now there is a MVP syntax and everything has just slowed down a lot.

Re: Local async executors and why they should be the default

#99
post #97

Earlier quoted context omitted.

One thing I hate about "async" systems in general is the absurdity of suddenly having two types of functions behave differently with same syntax. And you have to add "await" keyword to async functions to make them synchoronous, i.e. behave normally. I find the Go's approach of "everything is synchronous, except when made asynchronous with the 'go' keyword" much more pleasant and much less error prone. Is there a Rust…

Go can do this because it’s closer to Java or C# than C. It has a runtime that gets compiled into every binary. Rust deliberately avoided that by design.

Indeed.

That means that the interop story for golang is horrible. Golang can somewha work with libraries with C bindings. But you can not publish a golang lib as a lib.so with C bindings because of the runtime.

Re: Local async executors and why they should be the default

#100
I'm sympathetic to this point but I think that:

a) Saying Node + Deno are good is a stretch. Node has horrible performance, even for simple routing. And I'll source that[0].

b) Saying that adding `Send + Sync + 'static` bounds is a serious burden is, to me, overstating things.

> the far better model for writing performant servers.

It's completely workload dependent. For a chat server it's almost definitely not going to be more performant and you may end up with worse latency.

> it only costs you friction everywhere else in your entire codebase, and quite often performance as well.

I am unconvinced tbh. I do not believe that adding Send + Sync + 'static bounds is onerous, I do not believe satisfying those bounds is hard (it's almost always just a matter of moving the value), and I do not believe that the vast majority of programs benefit from TPC architecture.

I recognize that there is a problem here - that we are optimizing for one runtime at the expense of others - but I am not convinced at this point that the problem matters.

[0] https://www.techempower.com/benchmarks/#section=data-r21&tes...

Post reply on HN