That is interesting. Is it that much complexity when using async Rust?
That is interesting. Is it that much complexity when using async Rust?
The network and disk IO is a tiny part (after the initial startup scan). Most of the work is CPU bound.
So async really doesn't get you much.
Async is also okay (not perfect) for CPU parallelism, with tokio::spawn etc., which I think is important for fast incremental compilation once everything is in RAM. To pick an example, let’s say you change the type of a widely used function, now you have to typecheck the entire project again.
If I were to design a language server, my hope would be to achieve both good IO interleaving and good CPU parallelism with a single async based design.
Not really —- there’s bowl of spaghetti worth of concurrency in LSP. All of the following things can happen at the same time, and they use the same state:
* several read-only requests from the client (compute completion *and* highlighting)
* write request from the client (the user typed a comment in Cargo.toml)
* write request from file system (user switched branches can Cargo.lock is now different)
* background project update (cargo metadata finished with a new project model)
* background compiler update (cargo check emitted a new diagnostic)
* updates on background “indexing” which were kicked in quiescent state
That’s significantly more logical concurrency than in a typical HTTP server, where each request is conceptually independent from every other and all the hard synchronization bits lie in the database.To manage this complexity, csp-style APIs (async/await and blocking threads) don’t really work, as there’s too much fundamental shared mutable state. What sort-of-works (but still is complex) is an explicit actor model, where you manually code the state machine and explicitly resolve the conflicts of the style “ok, I got results from cargo metadata, so I could spawn cargo check, but I also know that the user changed Cargo.toml in the meantime, so the results of metadata are actually stale and I better re-run that”.
Secondly adding some parallelization in normal "vanilla" Rust with std seems rather easy with channels and Rayon, so I'm not looking to retry async rust if I don't have to.
But I agree that we’re past that point at least for simple async worker threads and simple IO. I don’t follow the progress that closely, but I think work is happening towards standardization there.
I think the same way, and was enthusiastic of async Rust at the beginning, but right now I think it has caused a mess in libraries. Maybe they did the standardization way too early? Maybe they could have waited until the async libraries can be separated from runtimes, which I think they are now planning to do.
If you start to dig through otherwise useful libraries you see a divide to those who async and those who don't, and top of that which runtime they have chosen. Mixing and matching between those is painful.
This state totally sucks for the authors of async-std who have an uphill battle getting traction in the tokio-dominated ecosystem, but from user perspective you can pretend the problem doesn't exist. Treat tokio as the standard library, as if it was hardcoded and you had no choice on the matter.