Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
338766a
worktree: Add a binary for benchmarking the initial worktree scan of a
osiewicz Oct 14, 2025
547e627
Add file/dir count to benchmarks
osiewicz Oct 14, 2025
78cf23a
Add simple benchmarks for fs operations
osiewicz Oct 15, 2025
dc5eb24
Move worktree benchmark into a separate crate
osiewicz Oct 15, 2025
fcb2432
fs: Replace smol in RealFs::load with background executor
osiewicz Oct 13, 2025
5ea65bf
Fix a bunch of tests
osiewicz Oct 13, 2025
058d5b7
Move worktree background scanner to use async locks
osiewicz Oct 13, 2025
edaf77e
Make worktree locks asynchronous
osiewicz Oct 13, 2025
75fb172
A bunch of extra methods on fs
osiewicz Oct 13, 2025
e2f53f3
Fix clippy
osiewicz Oct 14, 2025
025dd5e
BG-fy read_dir.. at least partially.
osiewicz Oct 14, 2025
32226d0
fix 'test_strip_whitespace_and_format_via_lsp'
osiewicz Oct 14, 2025
a1975dc
read_link
osiewicz Oct 14, 2025
96c57df
Minor opt
osiewicz Oct 14, 2025
23d84e2
Experiment: Bundle metadata with directory info
osiewicz Oct 14, 2025
6461240
Speculative: get rid of smol::unblock from windows' file_id impl
osiewicz Oct 14, 2025
1cfe4ec
Fix Windows/Linux build
osiewicz Oct 15, 2025
38618f6
Revert "Fix Windows/Linux build"
osiewicz Oct 15, 2025
72ed106
Revert "Speculative: get rid of smol::unblock from windows' file_id i…
osiewicz Oct 15, 2025
50aaf89
Revert "Experiment: Bundle metadata with directory info"
osiewicz Oct 15, 2025
ade9dfe
CI fixes
osiewicz Oct 15, 2025
0403e5c
Add missing license
osiewicz Oct 15, 2025
816bcff
Merge branch 'main' into fs-use-bg-executor
osiewicz Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ members = [

"tooling/perf",
"tooling/workspace-hack",
"tooling/xtask",
"tooling/xtask", "crates/fs_benchmarks", "crates/worktree_benchmarks",
]
default-members = ["crates/zed"]

Expand Down Expand Up @@ -455,6 +455,7 @@ async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.0"
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12509,6 +12509,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
)
.await;

cx.run_until_parked();
// Set up a buffer white some trailing whitespace and no trailing newline.
cx.set_state(
&[
Expand Down
93 changes: 67 additions & 26 deletions crates/fs/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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()) {
Expand All @@ -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)]
Expand All @@ -707,15 +738,25 @@ 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)
}

async fn read_dir(
&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:?}")),
});
Expand Down
13 changes: 13 additions & 0 deletions crates/fs_benchmarks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "fs_benchmarks"
version = "0.1.0"
publish.workspace = true
edition.workspace = true

[dependencies]
fs.workspace = true
gpui = {workspace = true, features = ["windows-manifest"]}
workspace-hack.workspace = true

[lints]
workspace = true
1 change: 1 addition & 0 deletions crates/fs_benchmarks/LICENSE-GPL
32 changes: 32 additions & 0 deletions crates/fs_benchmarks/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use fs::Fs;
use gpui::{AppContext, Application};
fn main() {
let Some(path_to_read) = std::env::args().nth(1) else {
println!("Expected path to read as 1st argument.");
return;
};

let _ = Application::headless().run(|cx| {
let fs = fs::RealFs::new(None, cx.background_executor().clone());
cx.background_spawn(async move {
let timer = std::time::Instant::now();
let result = fs.load_bytes(path_to_read.as_ref()).await;
let elapsed = timer.elapsed();
if let Err(e) = result {
println!("Failed `load_bytes` after {elapsed:?} with error `{e}`");
} else {
println!("Took {elapsed:?} to read {} bytes", result.unwrap().len());
};
let timer = std::time::Instant::now();
let result = fs.metadata(path_to_read.as_ref()).await;
let elapsed = timer.elapsed();
if let Err(e) = result {
println!("Failed `metadata` after {elapsed:?} with error `{e}`");
} else {
println!("Took {elapsed:?} to query metadata");
};
std::process::exit(0);
})
.detach();
});
}
1 change: 1 addition & 0 deletions crates/worktree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ test-support = [

[dependencies]
anyhow.workspace = true
async-lock.workspace = true
clock.workspace = true
collections.workspace = true
fs.workspace = true
Expand Down
Loading
Loading