language: Move language server update to background when it takes too long (#43164)

Closes https://github.com/zed-industries/zed/issues/42360

If updating a language server takes longer than 10 seconds, we now fall
back to launching the currently installed version (if exists) and
continue downloading the update in the background.

Release Notes:

- Improved language server updates for slow connection, now Zed launches
existing server if the update is taking too long.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Smit Barmase
2025-11-21 14:19:55 +05:30
committed by GitHub
co-authored by Piotr Osiewicz
parent bb514c158e
commit e2f6422b3e
7 changed files with 161 additions and 157 deletions
+86 -65
View File
@@ -28,6 +28,8 @@ use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet, IndexSet};
use futures::Future;
use futures::future::LocalBoxFuture;
use futures::lock::OwnedMutexGuard;
use gpui::{App, AsyncApp, Entity, SharedString};
pub use highlight_map::HighlightMap;
use http_client::HttpClient;
@@ -51,7 +53,6 @@ use std::{
mem,
ops::{DerefMut, Range},
path::{Path, PathBuf},
pin::Pin,
str,
sync::{
Arc, LazyLock,
@@ -152,7 +153,14 @@ pub struct Location {
}
type ServerBinaryCache = futures::lock::Mutex<Option<(bool, LanguageServerBinary)>>;
type DownloadableLanguageServerBinary = LocalBoxFuture<'static, Result<LanguageServerBinary>>;
pub type LanguageServerBinaryLocations = LocalBoxFuture<
'static,
(
Result<LanguageServerBinary>,
Option<DownloadableLanguageServerBinary>,
),
>;
/// Represents a Language Server, with certain cached sync properties.
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
/// once at startup, and caches the results.
@@ -162,7 +170,7 @@ pub struct CachedLspAdapter {
pub disk_based_diagnostics_progress_token: Option<String>,
language_ids: HashMap<LanguageName, String>,
pub adapter: Arc<dyn LspAdapter>,
cached_binary: ServerBinaryCache,
cached_binary: Arc<ServerBinaryCache>,
}
impl Debug for CachedLspAdapter {
@@ -209,18 +217,15 @@ impl CachedLspAdapter {
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
let mut cached_binary = self.cached_binary.lock().await;
self.adapter
.clone()
.get_language_server_command(
delegate,
toolchains,
binary_options,
&mut cached_binary,
cx,
)
.await
) -> LanguageServerBinaryLocations {
let cached_binary = self.cached_binary.clone().lock_owned().await;
self.adapter.clone().get_language_server_command(
delegate,
toolchains,
binary_options,
cached_binary,
cx.clone(),
)
}
pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
@@ -513,14 +518,14 @@ pub trait DynLspInstaller {
pre_release: bool,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary>;
fn get_language_server_command<'a>(
fn get_language_server_command(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
cached_binary: &'a mut Option<(bool, LanguageServerBinary)>,
cx: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>>;
cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
cx: AsyncApp,
) -> LanguageServerBinaryLocations;
}
#[async_trait(?Send)]
@@ -562,15 +567,16 @@ where
binary
}
}
fn get_language_server_command<'a>(
fn get_language_server_command(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
toolchain: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
cached_binary: &'a mut Option<(bool, LanguageServerBinary)>,
cx: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
mut cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
mut cx: AsyncApp,
) -> LanguageServerBinaryLocations {
async move {
let cached_binary_deref = cached_binary.deref_mut();
// First we check whether the adapter can give us a user-installed binary.
// If so, we do *not* want to cache that, because each worktree might give us a different
// binary:
@@ -584,7 +590,7 @@ where
// for each worktree we might have open.
if binary_options.allow_path_lookup
&& let Some(binary) = self
.check_if_user_installed(delegate.as_ref(), toolchain, cx)
.check_if_user_installed(delegate.as_ref(), toolchain, &mut cx)
.await
{
log::info!(
@@ -593,62 +599,77 @@ where
binary.path,
binary.arguments
);
return Ok(binary);
return (Ok(binary), None);
}
anyhow::ensure!(
binary_options.allow_binary_download,
"downloading language servers disabled"
);
if !binary_options.allow_binary_download {
return (
Err(anyhow::anyhow!("downloading language servers disabled")),
None,
);
}
if let Some((pre_release, cached_binary)) = cached_binary
if let Some((pre_release, cached_binary)) = cached_binary_deref
&& *pre_release == binary_options.pre_release
{
return Ok(cached_binary.clone());
return (Ok(cached_binary.clone()), None);
}
let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await
else {
anyhow::bail!("no language server download dir defined")
return (
Err(anyhow::anyhow!("no language server download dir defined")),
None,
);
};
let mut binary = self
.try_fetch_server_binary(
&delegate,
container_dir.to_path_buf(),
binary_options.pre_release,
cx,
)
.await;
let last_downloaded_binary = self
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
.await
.context(
"did not find existing language server binary, falling back to downloading",
);
let download_binary = async move {
let mut binary = self
.try_fetch_server_binary(
&delegate,
container_dir.to_path_buf(),
binary_options.pre_release,
&mut 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
{
log::info!(
"failed to fetch newest version of language server {:?}. \
error: {:?}, falling back to using {:?}",
self.name(),
error,
prev_downloaded_binary.path
);
binary = Ok(prev_downloaded_binary);
} else {
delegate.update_status(
self.name(),
BinaryStatus::Failed {
error: format!("{error:?}"),
},
);
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
{
log::info!(
"failed to fetch newest version of language server {:?}. \
error: {:?}, falling back to using {:?}",
self.name(),
error,
prev_downloaded_binary.path
);
binary = Ok(prev_downloaded_binary);
} else {
delegate.update_status(
self.name(),
BinaryStatus::Failed {
error: format!("{error:?}"),
},
);
}
}
}
if let Ok(binary) = &binary {
*cached_binary = Some((binary_options.pre_release, binary.clone()));
}
if let Ok(binary) = &binary {
*cached_binary = Some((binary_options.pre_release, binary.clone()));
}
binary
binary
}
.boxed_local();
(last_downloaded_binary, Some(download_binary))
}
.boxed_local()
}