Correctly handle network issues during LSP server installation (#9460)

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
80bc6c8cc8/crates/language/src/language.rs (L339)

bailing out of all installation related-methods up to here:


80bc6c8cc8/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
This commit is contained in:
Kirill Bulatov 2024-03-17 15:41:00 +02:00 committed by GitHub
parent 80bc6c8cc8
commit 9df92c3fb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 26 deletions

View file

@ -326,43 +326,25 @@ pub trait LspAdapter: 'static + Send + Sync {
.context("failed to create container directory")?; .context("failed to create container directory")?;
} }
if let Some(task) = self.will_fetch_server(&delegate, cx) { let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await;
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);
if let Err(error) = binary.as_ref() { if let Err(error) = binary.as_ref() {
if let Some(prev_downloaded_binary) = self if let Some(prev_downloaded_binary) = self
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
.await .await
{ {
delegate.update_status(name.clone(), LanguageServerBinaryStatus::Cached); delegate.update_status(self.name(), LanguageServerBinaryStatus::Cached);
log::info!( log::info!(
"failed to fetch newest version of language server {:?}. falling back to using {:?}", "failed to fetch newest version of language server {:?}. falling back to using {:?}",
name.clone(), self.name(),
prev_downloaded_binary.path.display() prev_downloaded_binary.path
); );
binary = Ok(prev_downloaded_binary); binary = Ok(prev_downloaded_binary);
} else { } else {
delegate.update_status( delegate.update_status(
name.clone(), self.name(),
LanguageServerBinaryStatus::Failed { LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error), error: format!("{error:?}"),
}, },
); );
} }
@ -500,6 +482,33 @@ pub trait LspAdapter: 'static + Send + Sync {
} }
} }
async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
adapter: &L,
delegate: &Arc<dyn LspAdapterDelegate>,
container_dir: PathBuf,
cx: &mut AsyncAppContext,
) -> Result<LanguageServerBinary> {
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)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CodeLabel { pub struct CodeLabel {
/// The text to display. /// The text to display.

View file

@ -1,4 +1,4 @@
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_trait::async_trait; use async_trait::async_trait;
use futures::{io::BufReader, StreamExt}; use futures::{io::BufReader, StreamExt};
@ -79,7 +79,7 @@ impl LspAdapter for RustLspAdapter {
.assets .assets
.iter() .iter()
.find(|asset| asset.name == asset_name) .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 { Ok(Box::new(GitHubLspBinaryVersion {
name: release.tag_name, name: release.tag_name,
url: asset.browser_download_url.clone(), url: asset.browser_download_url.clone(),