fs: Replace a bunch of uses of smol::fs with manual impls (#40172)

smol::fs uses a separate threadpool, which is a bit yuck.

This PR also added a benchmark you can use to run a full worktree scan
(initial one, that is) for arbitrary worktree.. and refactored worktree
scanner to use async locks, as otherwise tests were deadlocking. :)
I've benchmarked it against Zed, Linux and Chromium and saw a ~60% drop
in initial worktree scan times across the board.
Release Notes:

- Significantly (3.3x speedup over the old implementation) improved
speed of Zed's worktree scanner, that's responsible for synchronizing
the state of your project with the state of files on hard drive.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Piotr Osiewicz
2025-10-16 14:49:34 +02:00
committed by GitHub
co-authored by Smit Barmase
parent 9c70ba7dcc
commit c37a2f885a
13 changed files with 432 additions and 188 deletions
+67 -26
View File
@@ -7,6 +7,7 @@ pub mod fs_watcher;
use anyhow::{Context as _, Result, anyhow};
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
use futures::stream::iter;
use gpui::App;
use gpui::BackgroundExecutor;
use gpui::Global;
@@ -562,12 +563,17 @@ impl Fs for RealFs {
async fn load(&self, path: &Path) -> Result<String> {
let path = path.to_path_buf();
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
Ok(text)
self.executor
.spawn(async move { Ok(std::fs::read_to_string(path)?) })
.await
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
let path = path.to_path_buf();
let bytes = smol::unblock(|| std::fs::read(path)).await?;
let bytes = self
.executor
.spawn(async move { std::fs::read(path) })
.await?;
Ok(bytes)
}
@@ -635,30 +641,46 @@ impl Fs for RealFs {
if let Some(path) = path.parent() {
self.create_dir(path).await?;
}
smol::fs::write(path, content).await?;
Ok(())
let path = path.to_owned();
let contents = content.to_owned();
self.executor
.spawn(async move {
std::fs::write(path, contents)?;
Ok(())
})
.await
}
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
Ok(smol::fs::canonicalize(path)
let path = path.to_owned();
self.executor
.spawn(async move {
std::fs::canonicalize(&path).with_context(|| format!("canonicalizing {path:?}"))
})
.await
.with_context(|| format!("canonicalizing {path:?}"))?)
}
async fn is_file(&self, path: &Path) -> bool {
smol::fs::metadata(path)
let path = path.to_owned();
self.executor
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) })
.await
.is_ok_and(|metadata| metadata.is_file())
}
async fn is_dir(&self, path: &Path) -> bool {
smol::fs::metadata(path)
let path = path.to_owned();
self.executor
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_dir()) })
.await
.is_ok_and(|metadata| metadata.is_dir())
}
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
let symlink_metadata = match smol::fs::symlink_metadata(path).await {
let path_buf = path.to_owned();
let symlink_metadata = match self
.executor
.spawn(async move { std::fs::symlink_metadata(&path_buf) })
.await
{
Ok(metadata) => metadata,
Err(err) => {
return match (err.kind(), err.raw_os_error()) {
@@ -669,19 +691,28 @@ impl Fs for RealFs {
}
};
let path_buf = path.to_path_buf();
let path_exists = smol::unblock(move || {
path_buf
.try_exists()
.with_context(|| format!("checking existence for path {path_buf:?}"))
})
.await?;
let is_symlink = symlink_metadata.file_type().is_symlink();
let metadata = match (is_symlink, path_exists) {
(true, true) => smol::fs::metadata(path)
.await
.with_context(|| "accessing symlink for path {path}")?,
_ => symlink_metadata,
let metadata = if is_symlink {
let path_buf = path.to_path_buf();
let path_exists = self
.executor
.spawn(async move {
path_buf
.try_exists()
.with_context(|| format!("checking existence for path {path_buf:?}"))
})
.await?;
if path_exists {
let path_buf = path.to_path_buf();
self.executor
.spawn(async move { std::fs::metadata(path_buf) })
.await
.with_context(|| "accessing symlink for path {path}")?
} else {
symlink_metadata
}
} else {
symlink_metadata
};
#[cfg(unix)]
@@ -707,7 +738,11 @@ impl Fs for RealFs {
}
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
let path = smol::fs::read_link(path).await?;
let path = path.to_owned();
let path = self
.executor
.spawn(async move { std::fs::read_link(&path) })
.await?;
Ok(path)
}
@@ -715,7 +750,13 @@ impl Fs for RealFs {
&self,
path: &Path,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
let path = path.to_owned();
let result = iter(
self.executor
.spawn(async move { std::fs::read_dir(path) })
.await?,
)
.map(|entry| match entry {
Ok(entry) => Ok(entry.path()),
Err(error) => Err(anyhow!("failed to read dir entry {error:?}")),
});