Live data from Hacker News

Tokio 1.0 – async runtime for Rust

tokio.rs

351–360 of 422 posts

Re: Tokio 1.0 – async runtime for Rust

#351
post #306

Earlier quoted context omitted.

> Maybe it is undesirable because that often times plain mono-thread synchronous is fast enough, easier to read, easier to debug and safe to handle to a junior ? Not everybody in a team has the same level of expertise. Shared-memory concurrency is pretty much always buggy, IME, even if your team thinks they're experts. > And Rust is not an interpreted language. IMHO, interpreted languages should just drop to a compil…

> Shared-memory concurrency is pretty much always buggy, IME, even if your team thinks they're experts. Writing bug-free shared memory concurrency programs with Go is trivial. Your opinion is based on outdated information.

I've definitely written buggy goroutine code before. AMA :p

I vaguely remember expecting a reference and getting a copy or vice versa...

I mean, I agree that Go is miles ahead of most other languages when it comes to helping prevent concurrency bugs, but it's sill tricky. Same with Rust.

Re: Tokio 1.0 – async runtime for Rust

#352
post #217

Earlier quoted context omitted.

I am not saying that the borrow checker cannot handle locks. Locks work great. (The borrow checker does not understand locks as a special construct, to be extra clear.) Did you read the post I linked? It lays out the details. I am happy to clarify if you don’t get the specifics.

I see now, I misunderstood your original post. You were saying async/await is necessary because futures work badly, not because all the alternatives (i.e. locks) work badly. Sorry, my mistake! Edit to add: futures work badly in every language, so there's no shame in the borrow checker not working with them. Edit 2: But in that case we're back to "why would Rust want async/await over (potentially green) threads with i…

Language support for green threads require a heavier runtime (so you'd pay the performance cost even when you didn't write async code).

Tokio basically is green threads as a library.

Re: Tokio 1.0 – async runtime for Rust

#353
post #126

Earlier quoted context omitted.

Based on this, the default value is 2MB: https://unix.stackexchange.com/questions/127602/default-stac... So that would mean a lot of memory for 1 million threads, 2TB of RAM. But you can change the default. With a 64k stack you'd use up ~68GB of RAM, which doesn't seem like a lot for 1 million threads and 1 million requests happening at the same time.

Also worth noting that the entire stack isn't allocated at once so 1 million threads would be using 2TB/68GB of virtual address space, not 2TB/68GB of physical memory.

That is indeed a very important fact to keep in mind! Thread stack sizes have been a problem with 32 bit systems where you quickly run out of virtual memory because the adress space is not large enough. With 64 bit that is not a problem anymore.

Re: Tokio 1.0 – async runtime for Rust

#354
post #311

Earlier quoted context omitted.

I've encountered the "wait on async things from sync code" issue several times, too. I have found that something like `block_on` from either `futures` or `futures_lite` often does the trick. https://docs.rs/futures-lite/1.11.3/futures_lite/future/fn.b...

Right, but depending on the tokio version (maybe) using block_on results in a hang or panic. I’ll see if futures_lite is any different, but I think it’s mostly on tokio’s side.

> depending on the tokio version (maybe) using block_on results in a hang or panic

Isn't it mostly a function of what runtime you're using? `block_on` with a single-threaded runtime can't run tasks elsewhere (since there's only one thread which you're blocking) so depending on the version it would either hang (before detection of that situation was added) or panic, with an error message saying to use a multithreaded runtime.

I completely agree that it's a pain in the ass though, especially since there are situations where tokio must be coerced into using anything but the basic scheduler (e.g. tests).

Re: Tokio 1.0 – async runtime for Rust

#355
post #336

Earlier quoted context omitted.

> it's a choice made by a particular external library Yeah I think it points to a culture problem. In some ways because dependency management is so easy with Cargo, I think it creates the temptation to just throw in some dependencies to make something work without truly understanding the overall complexity of what you're creating. Something very similar happens in the NodeJS world. > it splits the ecosystem This is s…

Agreed with your point about Cargo. It's a double edged sword. We absolutely have an NPM/leftpad culture in Rust. Is that better or worse than C and C++ where dependencies are so painful that you end up reinventing the wheel most of the time? I honestly don't know.

Yes I think it's a really difficult problem to be honest. I am definitely grateful for how easy it is to make rust projects reproducible, but it's not without disadvantages.

Re: Tokio 1.0 – async runtime for Rust

#356

Earlier quoted context omitted.

> Shared-memory concurrency is pretty much always buggy, IME, even if your team thinks they're experts. Writing bug-free shared memory concurrency programs with Go is trivial. Your opinion is based on outdated information.

I've definitely written buggy goroutine code before. AMA :p I vaguely remember expecting a reference and getting a copy or vice versa... I mean, I agree that Go is miles ahead of most other languages when it comes to helping prevent concurrency bugs, but it's sill tricky. Same with Rust.

Go makes concurrency about as tricky as a reasonably complex data structure. can you still write a bug? Of course. Is it so tricky that bugs are inevitable, or even common? Absolutely not.

Re: Tokio 1.0 – async runtime for Rust

#357

Earlier quoted context omitted.

> This is definitely a good thing. All computations should me "marked" as total or effectful with various possible effects (blocking, async, nondeterministic, possibly non-terminating etc etc). Marking functions for side-effects would be a good thing but it isn't what function coloring means in this context. An async function and a normal function are semantically the same, they just have different syntaxes and you c…

> An async function and a normal function are semantically the same But they are not, AsyncIO and BlockingIO are different side effects, thus you have different types of computation, that's exactly what I'm talking about. In languages with monads or algebraic effects these would have different types. You don't say that Lists and Arrays are semantically the same, despite being similar sequential collections, they stil…

I don't think considering the blocking strategy an effect is very useful. Even in async context in complex enough applications functions can call other functions including your own, so async is not enough to guarantee reentrancy.

I do agree that parametrizing over the blocking strategy is a great idea, but languages that simply provide an async syntactic marker don't necessarily allow that, and if your language is powerful enough you do not need the annotation in the first place.

Re: Tokio 1.0 – async runtime for Rust

#358

Earlier quoted context omitted.

> but to run those Future objects, you must repeatedly call their poll method Oh man. C++'s std::future has the same silliness. I've come to the conclusion that futures/promises are a dumb abstraction.

What about this bugs you? You know that it’s not like, a busy loop calling poll all the time, right?

Well, I took the phrase 'repeatedly call' to mean pretty much that!

What bugs me about it, if I'm understanding it correctly, is that you have two options once an async call is issued:

- the calling thread effectively waits for completion. This is fine if a fork/join pattern is useful to you (i.e. issue N async calls and then wait for N completions). This isn't proper asynchrony though.

- the future is pushed on to a thread that does nothing but poll for completions. This effectively imposes an O(n) inefficiency into your code.

Re: Tokio 1.0 – async runtime for Rust

#359

Earlier quoted context omitted.

That is also the maximum stack size which shouldn't normally be reached. You'll have to be careful how you use memory when you're handling a million clients, either as async or threaded.

The point is that each stack need to be big enough for the worst case. That means it does not really scales to start many thousands of threads. While the futures themselves used in async code can be kept relatively small, as they only need to contain the state needed while awaiting.

In theory a smart enough OS (or runtime) should be able to reclaim any memory beyond the stack pointer (plus redzone) at any time without preserving its content and shrink back the stack. Because of signal handlers that memory is to be considered volatile anyway.

It might not be worth doing it in practice, but it is something to keep on mind.

Re: Tokio 1.0 – async runtime for Rust

#360
post #350
post #348

Earlier quoted context omitted.

So I would not say that the borrow checker is a middle-man. The borrow checker does impose constraints on programming, but at the end of the day it's only relevant at compile time, and you still end up with code which maps in a predictable way to the hardware. If you hand me a synchronous rust code, I can imagine, at least in some approximate way, which set of assembly code would be equivalent. An async runtime is a…

Fair distinction, thanks for making it! My async use cases so far have been in a realm where everything modeled was everything I cared about, and those specifics of the runtime didn't become relevant. I wanted to refer to your example because I do see how that is a thing that needs to be explicitly modeled. I'd be curious to hear about examples where the runtime did or would surprise you!

I don't have a ton of experience writing async rust programs, but I can imagine some types of problems which might come up:

- So what if I am implementing a high-throughput, performance critical system which makes heavy use of async, and under certain circumstances the runtime I'm using falls off a performance cliff. It's going to be difficult to diagnose and solve this problem, because the critical path of my program actually winds through a library which is essentially a black box to me.

- What if I have two dependencies, and each one internally depends on a separate async runtime. And what if each of these runtimes is designed with the assumption that it is the main owner of system resources, like threads. There may be conflicts which are very difficult to understand but have effects on the performance of my program.

I think fundamentally, an issue with this type of "middleware" is that by its nature, an async runtime, like Tokio for example, has to be implemented with a lot of assumptions about how "the generic program" should optimally handle async. It may work great for the vast majority of use-cases, but fundamentally whenever you design a super general, abstract system like this you have to make tradeoffs.

In some ways Rust has taken probably the best possible approach to this, by making it modular and allowing you to bring your own runtime, but I think in practice, if the use of async continues to become pervasive in Rust and certain libraries get locked into certain ecosystems, it will not be so easy in practice to take advantage of that modularity.

Post reply on HN