3 ms·
The more relevant point is that async is good for IO concurrency. But a LSP mostly deals with a single request at a time by a single client. The network and di
by the_duke 5y ago
The more relevant point is that async is good for IO concurrency. But a LSP mostly deals with a single request at a time by a single client.
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.
- codeflo 5y agoThe “initial scan” scenario also happens after git operations like pull, checkout etc. When working in a team where you pull and push often, it’s a real quality of life improvement if the IDE adapts quickly, so I wouldn’t dismiss that completely. 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.
- matklad 5y ago>But a LSP mostly deals with a single request at a time by a single client. 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”.