From 9df92c3fb6a63eba183593c8f1106e9f0c4d7ebf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 17 Mar 2024 15:41:00 +0200 Subject: [PATCH] Correctly handle network issues during LSP server installation (#9460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/zed-industries/zed/issues/9458 When flying in a plane being totally offline, I've discovered that my Rust projects do not have any LSP support and rust-analyzer disappeared out of `~/Library/Application Support/Zed/languages/rust-analyzer/` directory. Looking at the [bad.log](https://github.com/zed-industries/zed/files/14627508/bad.log), it appears that `get_language_server_command` tries to find a newer LSP server version and fails on https://github.com/zed-industries/zed/blob/80bc6c8cc800e23ba79723b0c46b731a62203224/crates/language/src/language.rs#L339 bailing out of all installation related-methods up to here: https://github.com/zed-industries/zed/blob/80bc6c8cc800e23ba79723b0c46b731a62203224/crates/project/src/project.rs#L2916 where the code thinks that the binary installation process had failed, cleans the existing directory and tries to install the language server again: ```log [2024-03-17T15:14:13+02:00 WARN isahc::handler] request completed with error: failed to resolve host name [2024-03-17T15:14:13+02:00 ERROR project] failed to start language server "rust-analyzer": error fetching latest release [2024-03-17T15:14:13+02:00 ERROR project] server stderr: Some("") [2024-03-17T15:14:13+02:00 INFO project] retrying installation of language server "rust-analyzer" in 1s [2024-03-17T15:14:13+02:00 ERROR util] crates/lsp/src/lsp.rs:720: oneshot canceled [2024-03-17T15:14:14+02:00 INFO project] About to spawn test binary [2024-03-17T15:14:14+02:00 WARN project] test binary failed to launch [2024-03-17T15:14:14+02:00 WARN project] test binary check failed [2024-03-17T15:14:14+02:00 INFO project] beginning to reinstall server [2024-03-17T15:14:14+02:00 INFO language::language_registry] deleting server container [2024-03-17T15:14:14+02:00 INFO language::language_registry] starting language server "rust-analyzer", path: "/Users/someonetoignore/work/other/local_test", id: 2 [2024-03-17T15:14:14+02:00 INFO language] fetching latest version of language server "rust-analyzer" [2024-03-17T15:14:14+02:00 WARN isahc::handler] request completed with error: failed to resolve host name [2024-03-17T15:14:14+02:00 ERROR project] failed to start language server "rust-analyzer": error fetching latest release [2024-03-17T15:14:14+02:00 ERROR project] server stderr: Some("") [2024-03-17T15:14:14+02:00 INFO project] retrying installation of language server "rust-analyzer" in 1s [2024-03-17T15:14:15+02:00 ERROR util] crates/languages/src/rust.rs:335: no cached binary [2024-03-17T15:14:15+02:00 INFO project] About to spawn test binary ............ ``` The PR extracts away all binary fetching-related code into a single method that does not fail the entire `get_language_server_command` and allows it to recover and reuse the existing binary: [good.log](https://github.com/zed-industries/zed/files/14627507/good.log) ```log [2024-03-17T15:12:24+02:00 INFO language::language_registry] starting language server "rust-analyzer", path: "/Users/someonetoignore/work/other/local_test", id: 1 [2024-03-17T15:12:24+02:00 INFO language] fetching latest version of language server "rust-analyzer" [2024-03-17T15:12:24+02:00 WARN isahc::handler] request completed with error: failed to resolve host name [2024-03-17T15:12:24+02:00 INFO language] failed to fetch newest version of language server LanguageServerName("rust-analyzer"). falling back to using "/Users/someonetoignore/Library/Application Support/Zed/languages/rust-analyzer/rust-analyzer-2024-03-11" [2024-03-17T15:12:24+02:00 INFO lsp] starting language server. binary path: "/Users/someonetoignore/Library/Application Support/Zed/languages/rust-analyzer/rust-analyzer-2024-03-11", working directory: "/Users/someonetoignore/work/other/local_test", args: [] ``` Release Notes: - Fixed language servers erased from the disk when project is opened offline --- crates/language/src/language.rs | 57 +++++++++++++++++++-------------- crates/languages/src/rust.rs | 4 +-- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 8c2596f11fa90f439144d4b5207a0f0c50513a5f..670256d87c94ff1e6deac525dd62be43450a92cb 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -326,43 +326,25 @@ pub trait LspAdapter: 'static + Send + Sync { .context("failed to create container directory")?; } - if let Some(task) = self.will_fetch_server(&delegate, cx) { - task.await?; - } - - let name = self.name(); - log::info!("fetching latest version of language server {:?}", name.0); - delegate.update_status( - name.clone(), - LanguageServerBinaryStatus::CheckingForUpdate, - ); - let latest_version = self.fetch_latest_server_version(delegate.as_ref()).await?; - - log::info!("downloading language server {:?}", name.0); - delegate.update_status(self.name(), LanguageServerBinaryStatus::Downloading); - let mut binary = self - .fetch_server_binary(latest_version, container_dir.to_path_buf(), delegate.as_ref()) - .await; - - delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded); + let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await; if let Err(error) = binary.as_ref() { if let Some(prev_downloaded_binary) = self .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) .await { - delegate.update_status(name.clone(), LanguageServerBinaryStatus::Cached); + delegate.update_status(self.name(), LanguageServerBinaryStatus::Cached); log::info!( "failed to fetch newest version of language server {:?}. falling back to using {:?}", - name.clone(), - prev_downloaded_binary.path.display() + self.name(), + prev_downloaded_binary.path ); binary = Ok(prev_downloaded_binary); } else { delegate.update_status( - name.clone(), + self.name(), LanguageServerBinaryStatus::Failed { - error: format!("{:?}", error), + error: format!("{error:?}"), }, ); } @@ -500,6 +482,33 @@ pub trait LspAdapter: 'static + Send + Sync { } } +async fn try_fetch_server_binary( + adapter: &L, + delegate: &Arc, + container_dir: PathBuf, + cx: &mut AsyncAppContext, +) -> Result { + if let Some(task) = adapter.will_fetch_server(delegate, cx) { + task.await?; + } + + let name = adapter.name(); + log::info!("fetching latest version of language server {:?}", name.0); + delegate.update_status(name.clone(), LanguageServerBinaryStatus::CheckingForUpdate); + let latest_version = adapter + .fetch_latest_server_version(delegate.as_ref()) + .await?; + + log::info!("downloading language server {:?}", name.0); + delegate.update_status(adapter.name(), LanguageServerBinaryStatus::Downloading); + let binary = adapter + .fetch_server_binary(latest_version, container_dir, delegate.as_ref()) + .await; + + delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded); + binary +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CodeLabel { /// The text to display. diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 2a58bc601559d5b4811708e01300d52310050428..5b83249fb326bcc3b7f0452789b5ba5314655e1c 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; @@ -79,7 +79,7 @@ impl LspAdapter for RustLspAdapter { .assets .iter() .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; Ok(Box::new(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url.clone(),