Live data from Hacker News

Building a shared vision for Async Rust

blog.rust-lang.org

41–50 of 139 posts

Re: Building a shared vision for Async Rust

#41
post #30
post #28

Earlier quoted context omitted.

Your comment touches on a few misconceptions I see a lot. Firstly, `reqwest` exposes both an async and a synchronous API, allowing the developer to choose which one to use. They are largely interchangeable code-wise. [1] Secondarily, and more broadly, async is possible to opt out of. You must understand that most web and network related libraries will be async by default for performance, because people who write in R…

"reqwest" exposes a synchronous API, but it's doing async stuff underneath. If you turn on logging, you can see 30 or so async events associated with a single HTTP client side request. It's starting up and shutting down a polling thread just to make one HTTP client request. You must understand that most web and network related libraries will be async by default for "performance" . That's what scares me - async contam…

Let me try to rephrase this in a way that doesn't pin the blame on "async enthusiasts" as people, and see if you agree:

Many years ago, well before Rust 1.0, Rust used its own M:N threading system, used segmented stacks, had it's own libuv-based event loop, etc. Also, it had garbage collection built into the language.

These were removed before 1.0, which made Rust a lot better as a systems language: you could reliably embed it into non-Rust programs, you could reliably interoperate with non-Rust libraries that didn't expect to be moved around threads, you didn't need to care about starting up the GC (or handling GC pauses), etc.

This was a good decision for Rust, and it turns out most of the things people wanted to do with these features could be done outside it - e.g., the borrow checker avoided the need for pervasive GC. (Though almost certainly not intentional, one side effect is that it distinguished Rust from Go: Go is great for standalone programs that need lightweight concurrency, but it's very bad at being embedded into other code and not the best choice if you're mostly calling FFI libraries.)

First, it would be good for Rust to stick to that decision. Rust should not regain a pausing GC in the standard library - similarly, it should not regain a thread manager in the standard library.

Second, it would be good for Rust libraries to work within the spirit of that decision. That's a lot harder, because part of the expectation when those features were removed was that some needs - notably around event-based processing - would be met by third-party libraries. As I understand it (and I might be totally wrong!), it was honestly a bit of luck that the borrow checker worked as well as it did and was ready at the right time, and the expectation was that someone would add a GC library and it would be widely used. However, it's very good that no widely-used GC library sprung up. In the same vein, it would be good for there to be no widely-used third-party thread manager library.

This might be hard, possibly requiring a borrow-checker-level miracle, but it's worth aiming for. And if there has to be a thread manager (or a garbage collector), it should not be part of core Rust.

Would you agree with that phrasing?

--

Incidentally, why does reqwest start up a polling thread when called in sync mode? Can't it do the polling on the main thread? (Or in other words, async programming doesn't imply multithreaded programming. I actually sort of expect that async programming is better suited to single-threaded programming with an event loop, because if you're okay with threads, you may as well just write synchronous code on threads! So either there is something subtle and very interesting here, or there's an easy fix, or I'm misunderstanding something badly.)

Re: Building a shared vision for Async Rust

#42
post #30
post #28

Earlier quoted context omitted.

Your comment touches on a few misconceptions I see a lot. Firstly, `reqwest` exposes both an async and a synchronous API, allowing the developer to choose which one to use. They are largely interchangeable code-wise. [1] Secondarily, and more broadly, async is possible to opt out of. You must understand that most web and network related libraries will be async by default for performance, because people who write in R…

"reqwest" exposes a synchronous API, but it's doing async stuff underneath. If you turn on logging, you can see 30 or so async events associated with a single HTTP client side request. It's starting up and shutting down a polling thread just to make one HTTP client request. You must understand that most web and network related libraries will be async by default for "performance" . That's what scares me - async contam…

That doesn't seem to be related to async? I don't know the details of rust's async implementation but that sounds like a problem with your application's setup -- you should be able to have a single threaded async executor that uses an event loop, or in simple cases, just calls poll/select directly?

To put it another way, it's unfortunate that particular synchronous API is implemented using threads, but there is nothing about async that implies one way or another that a synchronous method will be implemented using threads -- I've seen plenty of (questionable) C functions that do similar things like using pthread_create and then pthread_join immediately after to fake a blocking task.

Re: Building a shared vision for Async Rust

#43
post #35
post #26

The main thing I want for Async Rust is the ability to not use it. What I'm doing involves a virtual world viewer with maybe a dozen threads. Some are compute bound. Some are talking to the GPU. Some are waiting for I/O. The normal situation is about 2 or 3 CPUs of work. Sometimes more. I don't want an async model, which assumes you're I/O bound, interfering with keeping all those CPUs busy. Already I've had to switc…

> The main thing I want for Async Rust is the ability to not use it. This is what many of us want. Sadly this doesn't seem to be a use-case many in rust are interested in supporting as a first class citizen (from what I've read at least). I remember seeing someone being down-voted into oblivion for the mere suggestion of some sort of basic fallback executor in the standard library. Rust's async story is a weird one.

Just to be clear, you are saying that many 3rd party libraries in the ecosystem are not interested in "supporting as a first class citizen", right? Because as for the language itself and the standard library, they are committed to support both as first-class citizens.

Re: Building a shared vision for Async Rust

#44
post #7

Is there any thought to including a default executor in the standard library? I think it's kind of an obstacle for beginners when the language/stdlib provide all the tools to write async code, but not to run it. I saw discussed in this talk the intent in allowing developers to provide their own executor based on the specifics of their use case: https://youtu.be/NNwK5ZPAJCk?t=1107 This makes sense to me; however, I fe…

I would worry this would end up like Python, where there is a default async executor in the standard library (asyncio) which was previously developed outside of the standard library (Tulip), but a) it's changed a fair bit between versions, even recently, as they figured out better ways to do things and b) there's been significant work on differently-structured executors (Curio and Trio, notably) as third-party libraries. As an end user of Python, I want to basically entirely use Trio, but there's a fair amount of async Python out there that's been written to assume asyncio.

(There is, at this point, an abstraction library called "anyio" which can run on either an asyncio or Trio event loop, but anyio's model is most strongly influenced by Trio's, because Trio's model is more structured - which means anyio couldn't have been written before Trio was developed and well-received.)

Given that Python has a strong "batteries included in the standard library" approach and Rust has a strong "we shipped a really good package manager with the language, and even the C 'int' type lives outside the standard library, in a package maintained by the Rust core team" approach, it seems like it would be very weird for Rust to repeat the mistake (at least in Rust's worldview) of shipping a default executor in the standard library that would turn into a de facto standard.

And given that Rust does have a really good package manager, adding a third-party dependency is pretty straightforward and doesn't seem like too much of an obstacle. (Frankly, the same is also true of Python, and I would certainly tell a beginner to make a virtualenv and pip install Trio. But since it didn't have it since day one, there's more of a cultural expectation to make the standard library useful out of the box.)

Re: Building a shared vision for Async Rust

#45
post #32
post #30

Earlier quoted context omitted.

"reqwest" exposes a synchronous API, but it's doing async stuff underneath. If you turn on logging, you can see 30 or so async events associated with a single HTTP client side request. It's starting up and shutting down a polling thread just to make one HTTP client request. You must understand that most web and network related libraries will be async by default for "performance" . That's what scares me - async contam…

I had understood your concern as wanting to avoid the complexity of asynchronous code execution in your codebase, I did not realize your concern is about writing very low level systems code. In that case, you are doing the right thing: libraries like ureq, minreq, Isahc, curl, and more all offer what you want. It is unclear to me what you mean by keeping the community "in check". There are a lot of people who rely on…

It's not that "low level". It's that it doesn't fit the model of "mostly waiting for the network". Here are some of the things I have going on:

- Incoming events UDP packets from multiple servers. These arrive at the client and go into a queue for processing.

- Refreshing the 3D window. This ties up one thread almost full time. At the beginning of each frame, it reads queued events that tell it what needs to change in the GPU. The rest of the time it feeds the GPU.

- Some incoming events require querying external HTTP servers to retrieve assets. When the results come back, they include compressed items which have to be decompressed, processed, and turned into GPU-ready textures or meshes. This is prioritized by how important it is to display that object right now, based on the viewpoint. So there are priority queues along with multithreading.

There's more, but you get the idea.

What's so great about Rust is that you can do stuff like this without crashing all the time, spending your life in the debugger, or recopying big objects to safely pass them around.

The previous implementation, in C++, looked a lot more like an "async" model. It had lots of "coroutines", mostly running off a single thread. It also had a few things running as independent threads because they were so CPU-intensive. It was very prone to short stalls that annoyed players. This happened because something had to do more work than expected and briefly stalled out the coroutine system. The killer in async systems is the subroutine that is usually fast but sometimes slow. So I've seen this problem done in "async" style, and it didn't work well.

I've previously done robotics work which had many asynchronous tasks. I've used QNX for that, and I've used ROS. There, you have a lot of intercommunicating processes, which works but has more overhead.

None of this maps well to an "async" model.

Part of the problem here may be that I've done a lot of multi-thread programming and am used to it. It's an alien approach to programmers who came up from Javascript. That's a big fraction of the web backend crowd.

(Personally, when I have to do a web service, I write it in Go. That's the use case for which Go is designed. It has the libraries for that, and the goroutine concept is well matched to that task.)

Re: Building a shared vision for Async Rust

#46
post #28
post #26

The main thing I want for Async Rust is the ability to not use it. What I'm doing involves a virtual world viewer with maybe a dozen threads. Some are compute bound. Some are talking to the GPU. Some are waiting for I/O. The normal situation is about 2 or 3 CPUs of work. Sometimes more. I don't want an async model, which assumes you're I/O bound, interfering with keeping all those CPUs busy. Already I've had to switc…

Your comment touches on a few misconceptions I see a lot. Firstly, `reqwest` exposes both an async and a synchronous API, allowing the developer to choose which one to use. They are largely interchangeable code-wise. [1] Secondarily, and more broadly, async is possible to opt out of. You must understand that most web and network related libraries will be async by default for performance, because people who write in R…

No, its not a misconception. It still uses tokio. It still triples my compile time.

Re: Building a shared vision for Async Rust

#47
post #39
post #34

Earlier quoted context omitted.

You want libraries to be able to offer implementations that are polymorphically async-or-not (perhaps using higher-kinded types). That's how I'm used to working in Scala.

This is also a problem over in Python-land. One of the interesting approaches is automatically generating non-async code from async code by editing out the async annotations (at the source level). The Trio folks call this "bleaching" the code, in reference to the "What color is your function?" blog post https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... complaining about how the common approach to async…

In Rust, you don't need to do this. If you want a blocking version of a non-blocking function, you spin on poll. Several async libraries ship a block_on function that does just that. If you can't do that, but don't want to bring in a proper big-boy executor; you can spin off another thread, have it block, and join it when it's done. (or use something like crossbeam's scoped threads)

Colored functions are a problem in JavaScript because it has to share an event loop with the rest of the browser. For Node.js and Python, that is less of an issue, but those languages are also either practically or actually single-threaded, which also means async behavior is contagious. Rust does not have this problem, at least not until you're writing WASM programs with it (in which case, you're restricted to sharing one thread with the rest of the browser again).

Re: Building a shared vision for Async Rust

#48
post #7

Is there any thought to including a default executor in the standard library? I think it's kind of an obstacle for beginners when the language/stdlib provide all the tools to write async code, but not to run it. I saw discussed in this talk the intent in allowing developers to provide their own executor based on the specifics of their use case: https://youtu.be/NNwK5ZPAJCk?t=1107 This makes sense to me; however, I fe…

I really really hope this never ever happens. Tokio is great and all, but enshrining it as the default would have a stifling effect and impose a lot of design decisions all over the place.

I do hope that it becomes more possible to write async code that is portable between executors. If there were more standard traits around the most common elements of an executor it'd make things a lot better imo.

Re: Building a shared vision for Async Rust

#49
post #32

Earlier quoted context omitted.

I had understood your concern as wanting to avoid the complexity of asynchronous code execution in your codebase, I did not realize your concern is about writing very low level systems code. In that case, you are doing the right thing: libraries like ureq, minreq, Isahc, curl, and more all offer what you want. It is unclear to me what you mean by keeping the community "in check". There are a lot of people who rely on…

You're replying to John Nagle, as in https://en.m.wikipedia.org/wiki/Nagle%27s_algorithm -- perhaps it would be worth considering his words further, before dismissing his concerns? Surely we can all agree that spinning up unnecessary threads is undesirable?

Appeal to authority fallacy

Re: Building a shared vision for Async Rust

#50
post #30

Earlier quoted context omitted.

"reqwest" exposes a synchronous API, but it's doing async stuff underneath. If you turn on logging, you can see 30 or so async events associated with a single HTTP client side request. It's starting up and shutting down a polling thread just to make one HTTP client request. You must understand that most web and network related libraries will be async by default for "performance" . That's what scares me - async contam…

That doesn't seem to be related to async? I don't know the details of rust's async implementation but that sounds like a problem with your application's setup -- you should be able to have a single threaded async executor that uses an event loop, or in simple cases, just calls poll/select directly? To put it another way, it's unfortunate that particular synchronous API is implemented using threads, but there is nothi…

Er? No, the point is that threads are what you want for cpu-bound tasks. Async does not deal well with long running cpu intensive jobs that hog the cpu without yield points.
Post reply on HN