Live data from Hacker News

Maybe Rust isn’t a good tool for massively concurrent, userspace software

bitbashing.io

191–200 of 624 posts

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#191

Admittedly, I’m no expert in async rust, but I’ve written several thousand lines of sync rust this month. One thing I’ve found is when rustc makes a particular approach hard to implement, it usually does so for a good reason (i.e. there is a better way to achieve a similar result). If you’re learning the language, I would suggest starting out with some more vanilla sync code, loops and if statements, get used to the…

I am curious what a "good async design" looks like, if we were going "all the way" with async and trying to design a highly scalable and maintainable and understandable server. The X11/Wayland post yesterday was interesting in how it described async drawing APIs in X11.

What does a good async API look like?

Also how do you prevent it spreading throughout a codebase?

I am trying to design a scalable architecture pattern for multithreaded and async servers. My design is that you have IO threads have asynchronous events into two halves "submit" and "handle". For example, system events from liburing or epoll are routed to other components. Those IO thread event loops run and block on epoll.poll/io_uring_wait_cqe.

For example, if you create a "tcp-connection" you can subscribe to async events that are "ready-for-writing" and "ready-for-reading". Ready-for-writing would take data out of a buffer (that was written to with a regular mutex) for the IO thread to send when EPOLLOUT/io_uring_prep_writev.

We can use the LMAX Disruptor pattern - multiproducer multiconsumer ringbuffers to communicate events between threads. Your application or thread pool threads have their own event loops and they service these ringbuffers.

I am working on a syntax to describe async event firing sequences. It looks like a bash pipeline, I call it statelines:

   initialstate1 initialstate2 = state1 | {state1a state1b state1c} {state2a state2b state2d} | state3
It first waits for "initialstate1" and "initialstate2" in any order, then it waits for "state1", then it waits for the states "state1a state1b state1c" and "state2a state2b state2d" in any order.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#193

Earlier quoted context omitted.

I find the criticisms a little strange - async doesn’t imply multithreaded, and you don’t need to annotate everything shared with magic keywords if you’re async within the same thread because there’s no sharing. Only one future at a time is running on the thread and they’re within the same context. When moving between threads I do what you suggest here and use channels to send signals rather than having a lot of shar…

> async doesn’t imply multithreaded Async the keyword doesn’t, but Tokio forces all of your async functions to be multi thread safe. And at the moment, tokio is almost exclusively the only async runtime used today. 95% of async libraries only support tokio. So you’re basically forced to write multi thread safe code even if you’d benefit more from a single thread event loop. Rust async’s set up is horrid and I wish th…

So with another async runtime it's possible to write async Rust that doesn't need to be thread-safe??? Can you show some example?

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#194
post #90
post #33

Earlier quoted context omitted.

Go also uses goroutines and channels to facilitate message passing, or as they describe it, "sharing memory by communicating." I imagine Rust to be a language far more similar to Go, in both use cases and functionality, than JS.

And in the end, almost everything ends up using Mutex, RWMutex, WaitGroup, Once, and some channels that exist only to ever be closed (like Context.Done), and only if you need to select around them. It's great, but message passing it is not.

Because all languages and developers assume that Erlang is only about message passing. And they completely ignore literally everything else: from immutable structures to the VM providing insane guarantees (like processes not crashing the VM, and monitoring)

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#195

I find myself in this weird corner when it comes to async rust. The guy's got a point in that doing a bunch of Arc, RwLock, and general sharing of state is going to get messy. Especially once you are sprinkling 'static all over the place, it infects everything, much like colored functions. I did this whole thing once back when I was starting off where I would Arc stuff, and try to be smart about borrow lifetimes. Tot…

You don't need Rust for that. You can even do it in JavaScript.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#196

I find myself in this weird corner when it comes to async rust. The guy's got a point in that doing a bunch of Arc, RwLock, and general sharing of state is going to get messy. Especially once you are sprinkling 'static all over the place, it infects everything, much like colored functions. I did this whole thing once back when I was starting off where I would Arc stuff, and try to be smart about borrow lifetimes. Tot…

The author does mention that you should probably stop at using Threads and passing data around via channels... but then mentions the C10K problem and says that sometimes you need more... but does not answer the question that I think is begging to be asked: does using Rust async with all the complications (Arc, cloning, Mutex whatever) does actually outperform Threads/channels?? Even if it does, by how much? It would be really interesting to know the answer. I have a feeling that Threads/channels may be more performant in practice, despite the imagined overhead.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#197
post #11

Earlier quoted context omitted.

> The lifetime of an Arc isn’t unknowable, it’s determined by where and how you hold it. In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. But that's nearly the opposite of what the borrow checker tries to do by statically bounding objects, at compile time. > maybe the disconnect in this article is that the author is coming at Rust and trying t…

> In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. These are not the same. The problem with GC'd systems is that you don't know when the GC will run and eat up your cpu cycles. It is impossible to determine when the memory will actually be freed in such systems. With ARC, you know exactly when you will release your last reference and that's wh…

> With ARC, you know exactly when you will release your last reference and that's when the resource is freed up.

It's more like "you notice when it happens". You don't know in advance when the last reference will be released (if you did, there would be no point in using reference counting).

> In terms of performance, ARC offers massive benefits because the memory that's being dereferenced is already in the cache.

It all depends on your access patterns. When ARC adjusts the reference counter, the object is invalidated in all other threads' caches. If this happens with high frequency, the cache misses absolutely demolish performance. GC simply does not have this problem.

> There's a reason people like ARC and stay away from GC when performance actually begins to matter.

If you're using a language without GC built in, you usually don't have a choice. When performance really begins to matter, people reach for things like hazard pointers.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#198

Earlier quoted context omitted.

They often implement soft preemption. Tokio and others like Glommio do. Usually, it's based on interrupts. The runtime schedules a timer to fire an interrupt, and some code is injected into the interrupt handler. This is used to keep track of task runtime quotas so they can yield as soon as possible afterward. This is the same technique used in Go and many others for preemption. If you don't add this, futures that do…

> You are right that it is not strictly necessary, but in practice, it is so helpful as a guard against the yielding problem that it's ubiquitous. This is honestly shocking to hear. I would think that if people had bugs in their programs they would want them to fail loudly so they can be fixed.

There's nothing buggy about a future that never yields because it can always make progress, but people prefer that a runtime doesn't let all other execution get starved by one operation. That makes it a problem that runtimes and schedulers work to solve, but not a bug that needs to be prevented at a language level. A runtime that doesn't solve it isn't buggy, but probably isn't friendly to use, like how Go used to have problems with tight loops and they put in changes to make them cause less starvation.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#199

We do not want red and blue functions. Any language that implements async / await as coroutines instead of green threads is making a fundamental CS mistake. https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... Concurrency's correct primitive is Hoare's Communicating Sequential Processes mapped onto green threads. Some languages that have it right are Java (since JDK17 - Java Virtual Threads), Go, Kotlin.

I think with stackful coroutines you lose low-overhead interoperability with C. Also, it possible to use stackless coroutines without introducing async/await 'colors'.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#200

I have been using "async/await is bad, use {feature name[0]}" as a litmus test for people who are generally bad at programming, especially so at concurrent flavour of such. Sure, Rust is certainly verbose and very strict how the ownership rules apply in the context of async, but this is a hard constraint of its memory safety model. We could probably do better while retaining all performance but this is by far one of…

> Async/await is here to stay and is the right abstraction, git good, and it's not even difficult to use anyway.

It's probably the right abstraction for Haskell, or any other language that works well with functional programming, lambdas and monads. Loom is a better fit for Java. Rust also would have probably been better off with something else. Effect handlers might have been a good choice.

Post reply on HN