gpui: Remove unsound await_on_background helper (#56132)

The function is unsound due to the classic fact that one can leak tasks,
sidestepping the blocking drop behavior resulting in a use after free.

Release Notes:

- N/A or Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth authored and GitHub committed 2026-05-08 09:30:34 +00:00
1 parent 042248e0ba
commit 9dc07e824b
17 files changed
+1052 -990

No files matched your search

+308 -271
View File
@@ -1,5 +1,5 @@
use anyhow::Result;
use anyhow::{Context as _, ensure};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use collections::HashMap;
use futures::future::BoxFuture;
@@ -45,6 +45,7 @@ use std::str::FromStr;
use std::{
borrow::Cow,
fmt::Write,
future::Future,
path::{Path, PathBuf},
sync::Arc,
};
@@ -447,92 +448,98 @@ impl LspInstaller for TyLspAdapter {
None
}
async fn fetch_server_binary(
fn fetch_server_binary(
&self,
latest_version: Self::BinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let GitHubLspBinaryVersion {
name,
url,
digest: expected_digest,
} = latest_version;
let destination_path = container_dir.join(format!("ty-{name}"));
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
async_fs::create_dir_all(&destination_path).await?;
let server_path = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
.join(Self::build_asset_name()?.0)
.join("ty"),
AssetKind::Zip => destination_path.clone().join("ty.exe"),
};
let binary = LanguageServerBinary {
path: server_path.clone(),
env: None,
arguments: vec!["server".into()],
};
let metadata_path = destination_path.with_extension("metadata");
let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
.await
.ok();
if let Some(metadata) = metadata {
let validity_check = async || {
delegate
.try_exec(LanguageServerBinary {
path: server_path.clone(),
arguments: vec!["--version".into()],
env: None,
})
.await
.inspect_err(|err| {
log::warn!("Unable to run {server_path:?} asset, redownloading: {err:#}",)
})
};
if let (Some(actual_digest), Some(expected_digest)) =
(&metadata.digest, &expected_digest)
{
if actual_digest == expected_digest {
if validity_check().await.is_ok() {
return Ok(binary);
}
} else {
log::info!(
"SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
);
}
} else if validity_check().await.is_ok() {
return Ok(binary);
}
}
download_server_binary(
&*delegate.http_client(),
&url,
expected_digest.as_deref(),
&destination_path,
Self::GITHUB_ASSET_KIND,
)
.await?;
make_file_executable(&server_path).await?;
remove_matching(&container_dir, |path| path != destination_path).await;
GithubBinaryMetadata::write_to_file(
&GithubBinaryMetadata {
metadata_version: 1,
async move {
let GitHubLspBinaryVersion {
name,
url,
digest: expected_digest,
},
&metadata_path,
)
.await?;
} = latest_version;
let destination_path = container_dir.join(format!("ty-{name}"));
Ok(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec!["server".into()],
})
async_fs::create_dir_all(&destination_path).await?;
let server_path = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
.join(Self::build_asset_name()?.0)
.join("ty"),
AssetKind::Zip => destination_path.clone().join("ty.exe"),
};
let binary = LanguageServerBinary {
path: server_path.clone(),
env: None,
arguments: vec!["server".into()],
};
let metadata_path = destination_path.with_extension("metadata");
let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
.await
.ok();
if let Some(metadata) = metadata {
let validity_check = async || {
delegate
.try_exec(LanguageServerBinary {
path: server_path.clone(),
arguments: vec!["--version".into()],
env: None,
})
.await
.inspect_err(|err| {
log::warn!(
"Unable to run {server_path:?} asset, redownloading: {err:#}",
)
})
};
if let (Some(actual_digest), Some(expected_digest)) =
(&metadata.digest, &expected_digest)
{
if actual_digest == expected_digest {
if validity_check().await.is_ok() {
return Ok(binary);
}
} else {
log::info!(
"SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
);
}
} else if validity_check().await.is_ok() {
return Ok(binary);
}
}
download_server_binary(
&*delegate.http_client(),
&url,
expected_digest.as_deref(),
&destination_path,
Self::GITHUB_ASSET_KIND,
)
.await?;
make_file_executable(&server_path).await?;
remove_matching(&container_dir, |path| path != destination_path).await;
GithubBinaryMetadata::write_to_file(
&GithubBinaryMetadata {
metadata_version: 1,
digest: expected_digest,
},
&metadata_path,
)
.await?;
Ok(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec!["server".into()],
})
}
}
async fn cached_server_binary(
@@ -777,60 +784,70 @@ impl LspInstaller for PyrightLspAdapter {
}
}
async fn fetch_server_binary(
fn fetch_server_binary(
&self,
latest_version: Self::BinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
let latest_version = latest_version.to_string();
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
let node = self.node.clone();
self.node
.npm_install_packages(
async move {
let server_path = container_dir.join(Self::SERVER_PATH);
let latest_version = latest_version.to_string();
node.npm_install_packages(
&container_dir,
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
)
.await?;
let env = delegate.shell_env().await;
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
async fn check_if_version_installed(
&self,
version: &Self::BinaryVersion,
container_dir: &PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = self
.node
.should_install_npm_package(
Self::SERVER_NAME.as_ref(),
&server_path,
container_dir,
VersionStrategy::Latest(version),
)
.await;
if should_install_language_server {
None
} else {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: self.node.binary_path().await.ok()?,
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
fn check_if_version_installed(
&self,
version: &Self::BinaryVersion,
container_dir: &PathBuf,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
let node = self.node.clone();
let version = version.clone();
let container_dir = container_dir.clone();
async move {
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = node
.should_install_npm_package(
Self::SERVER_NAME.as_ref(),
&server_path,
&container_dir,
VersionStrategy::Latest(&version),
)
.await;
if should_install_language_server {
None
} else {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: node.binary_path().await.ok()?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
@@ -1949,46 +1966,50 @@ impl LspInstaller for PyLspAdapter {
Ok(())
}
async fn fetch_server_binary(
fn fetch_server_binary(
&self,
_: (),
_: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
let pip_path = venv.join(BINARY_DIR).join("pip3");
ensure!(
util::command::new_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server[all]")
.arg("--upgrade")
.output()
.await?
.status
.success(),
"python-lsp-server[all] installation failed"
);
ensure!(
util::command::new_command(pip_path)
.arg("install")
.arg("pylsp-mypy")
.arg("--upgrade")
.output()
.await?
.status
.success(),
"pylsp-mypy installation failed"
);
let pylsp = venv.join(BINARY_DIR).join("pylsp");
ensure!(
delegate.which(pylsp.as_os_str()).await.is_some(),
"pylsp installation was incomplete"
);
Ok(LanguageServerBinary {
path: pylsp,
env: None,
arguments: vec![],
})
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
async move {
let venv = Self::ensure_venv(delegate.as_ref()).await?;
let pip_path = venv.join(BINARY_DIR).join("pip3");
ensure!(
util::command::new_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server[all]")
.arg("--upgrade")
.output()
.await?
.status
.success(),
"python-lsp-server[all] installation failed"
);
ensure!(
util::command::new_command(pip_path)
.arg("install")
.arg("pylsp-mypy")
.arg("--upgrade")
.output()
.await?
.status
.success(),
"pylsp-mypy installation failed"
);
let pylsp = venv.join(BINARY_DIR).join("pylsp");
ensure!(
delegate.which(pylsp.as_os_str()).await.is_some(),
"pylsp installation was incomplete"
);
Ok(LanguageServerBinary {
path: pylsp,
env: None,
arguments: vec![],
})
}
}
async fn cached_server_binary(
@@ -2229,60 +2250,70 @@ impl LspInstaller for BasedPyrightLspAdapter {
}
}
async fn fetch_server_binary(
fn fetch_server_binary(
&self,
latest_version: Self::BinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
let latest_version = latest_version.to_string();
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
let node = self.node.clone();
self.node
.npm_install_packages(
async move {
let server_path = container_dir.join(Self::SERVER_PATH);
let latest_version = latest_version.to_string();
node.npm_install_packages(
&container_dir,
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
)
.await?;
let env = delegate.shell_env().await;
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
async fn check_if_version_installed(
&self,
version: &Self::BinaryVersion,
container_dir: &PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = self
.node
.should_install_npm_package(
Self::SERVER_NAME.as_ref(),
&server_path,
container_dir,
VersionStrategy::Latest(version),
)
.await;
if should_install_language_server {
None
} else {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: self.node.binary_path().await.ok()?,
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
fn check_if_version_installed(
&self,
version: &Self::BinaryVersion,
container_dir: &PathBuf,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
let node = self.node.clone();
let version = version.clone();
let container_dir = container_dir.clone();
async move {
let server_path = container_dir.join(Self::SERVER_PATH);
let should_install_language_server = node
.should_install_npm_package(
Self::SERVER_NAME.as_ref(),
&server_path,
&container_dir,
VersionStrategy::Latest(&version),
)
.await;
if should_install_language_server {
None
} else {
let env = delegate.shell_env().await;
Some(LanguageServerBinary {
path: node.binary_path().await.ok()?,
env: Some(env),
arguments: vec![server_path.into(), "--stdio".into()],
})
}
}
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
@@ -2566,89 +2597,95 @@ impl LspInstaller for RuffLspAdapter {
})
}
async fn fetch_server_binary(
fn fetch_server_binary(
&self,
latest_version: GitHubLspBinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let GitHubLspBinaryVersion {
name,
url,
digest: expected_digest,
} = latest_version;
let destination_path = container_dir.join(format!("ruff-{name}"));
let server_path = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
.join(Self::build_asset_name()?.0)
.join("ruff"),
AssetKind::Zip => destination_path.clone().join("ruff.exe"),
};
delegate: &Arc<dyn LspAdapterDelegate>,
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
let delegate = delegate.clone();
let binary = LanguageServerBinary {
path: server_path.clone(),
env: None,
arguments: vec!["server".into()],
};
let metadata_path = destination_path.with_extension("metadata");
let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
.await
.ok();
if let Some(metadata) = metadata {
let validity_check = async || {
delegate
.try_exec(LanguageServerBinary {
path: server_path.clone(),
arguments: vec!["--version".into()],
env: None,
})
.await
.inspect_err(|err| {
log::warn!("Unable to run {server_path:?} asset, redownloading: {err:#}",)
})
};
if let (Some(actual_digest), Some(expected_digest)) =
(&metadata.digest, &expected_digest)
{
if actual_digest == expected_digest {
if validity_check().await.is_ok() {
return Ok(binary);
}
} else {
log::info!(
"SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
);
}
} else if validity_check().await.is_ok() {
return Ok(binary);
}
}
download_server_binary(
&*delegate.http_client(),
&url,
expected_digest.as_deref(),
&destination_path,
Self::GITHUB_ASSET_KIND,
)
.await?;
make_file_executable(&server_path).await?;
remove_matching(&container_dir, |path| path != destination_path).await;
GithubBinaryMetadata::write_to_file(
&GithubBinaryMetadata {
metadata_version: 1,
async move {
let GitHubLspBinaryVersion {
name,
url,
digest: expected_digest,
},
&metadata_path,
)
.await?;
} = latest_version;
let destination_path = container_dir.join(format!("ruff-{name}"));
let server_path = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
.join(Self::build_asset_name()?.0)
.join("ruff"),
AssetKind::Zip => destination_path.clone().join("ruff.exe"),
};
Ok(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec!["server".into()],
})
let binary = LanguageServerBinary {
path: server_path.clone(),
env: None,
arguments: vec!["server".into()],
};
let metadata_path = destination_path.with_extension("metadata");
let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
.await
.ok();
if let Some(metadata) = metadata {
let validity_check = async || {
delegate
.try_exec(LanguageServerBinary {
path: server_path.clone(),
arguments: vec!["--version".into()],
env: None,
})
.await
.inspect_err(|err| {
log::warn!(
"Unable to run {server_path:?} asset, redownloading: {err:#}",
)
})
};
if let (Some(actual_digest), Some(expected_digest)) =
(&metadata.digest, &expected_digest)
{
if actual_digest == expected_digest {
if validity_check().await.is_ok() {
return Ok(binary);
}
} else {
log::info!(
"SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
);
}
} else if validity_check().await.is_ok() {
return Ok(binary);
}
}
download_server_binary(
&*delegate.http_client(),
&url,
expected_digest.as_deref(),
&destination_path,
Self::GITHUB_ASSET_KIND,
)
.await?;
make_file_executable(&server_path).await?;
remove_matching(&container_dir, |path| path != destination_path).await;
GithubBinaryMetadata::write_to_file(
&GithubBinaryMetadata {
metadata_version: 1,
digest: expected_digest,
},
&metadata_path,
)
.await?;
Ok(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec!["server".into()],
})
}
}
async fn cached_server_binary(