mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
fs: Defer initializing poll watcher until after initial worktree scan (#56207)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #56021 Closes #56100 Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
parent
1e018436d8
commit
5e62281357
2 changed files with 214 additions and 4 deletions
|
|
@ -140,6 +140,7 @@ pub struct LocalWorktree {
|
|||
settings: WorktreeSettings,
|
||||
share_private_files: bool,
|
||||
scanning_enabled: bool,
|
||||
force_defer_watch: bool,
|
||||
}
|
||||
|
||||
pub struct PathPrefixScanRequest {
|
||||
|
|
@ -504,6 +505,7 @@ impl Worktree {
|
|||
visible,
|
||||
settings,
|
||||
scanning_enabled,
|
||||
force_defer_watch: false,
|
||||
};
|
||||
worktree.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx);
|
||||
Worktree::Local(worktree)
|
||||
|
|
@ -1151,6 +1153,7 @@ impl LocalWorktree {
|
|||
let next_entry_id = self.next_entry_id.clone();
|
||||
let fs = self.fs.clone();
|
||||
let scanning_enabled = self.scanning_enabled;
|
||||
let force_defer_watch = self.force_defer_watch;
|
||||
let track_git_repositories = self.visible;
|
||||
let settings = self.settings.clone();
|
||||
let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
|
||||
|
|
@ -1158,7 +1161,10 @@ impl LocalWorktree {
|
|||
let abs_path = snapshot.abs_path.as_path().to_path_buf();
|
||||
let background = cx.background_executor().clone();
|
||||
async move {
|
||||
let (events, watcher) = if scanning_enabled {
|
||||
let defer_watch =
|
||||
force_defer_watch || (scanning_enabled && fs::requires_poll_watcher(&abs_path));
|
||||
|
||||
let (events, watcher) = if scanning_enabled && !defer_watch {
|
||||
fs.watch(&abs_path, FS_WATCH_LATENCY).await
|
||||
} else {
|
||||
(Box::pin(stream::pending()) as _, Arc::new(NullWatcher) as _)
|
||||
|
|
@ -1192,11 +1198,10 @@ impl LocalWorktree {
|
|||
watcher,
|
||||
track_git_repositories,
|
||||
is_single_file,
|
||||
defer_watch,
|
||||
};
|
||||
|
||||
scanner
|
||||
.run(Box::pin(events.map(|events| events.into_iter().collect())))
|
||||
.await;
|
||||
scanner.run(events).await;
|
||||
}
|
||||
});
|
||||
let scan_state_updater = cx.spawn(async move |this, cx| {
|
||||
|
|
@ -2053,6 +2058,12 @@ impl LocalWorktree {
|
|||
self.snapshot.update_abs_path(new_path, root_name);
|
||||
self.restart_background_scanners(cx);
|
||||
}
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn set_defer_watch(&mut self, defer: bool, cx: &mut Context<Worktree>) {
|
||||
self.force_defer_watch = defer;
|
||||
self.restart_background_scanners(cx);
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn repositories(&self) -> Vec<Arc<Path>> {
|
||||
self.git_repositories
|
||||
|
|
@ -3946,6 +3957,7 @@ struct BackgroundScanner {
|
|||
/// Whether this is a single-file worktree (root is a file, not a directory).
|
||||
/// Used to determine if we should give up after repeated canonicalization failures.
|
||||
is_single_file: bool,
|
||||
defer_watch: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
|
|
@ -4087,6 +4099,37 @@ impl BackgroundScanner {
|
|||
|
||||
self.send_status_update(false, SmallVec::new(), &[]).await;
|
||||
|
||||
if self.defer_watch {
|
||||
let (events, watcher) = self
|
||||
.fs
|
||||
.watch(root_abs_path.as_path(), FS_WATCH_LATENCY)
|
||||
.await;
|
||||
self.watcher = watcher;
|
||||
fs_events_rx = Box::pin(events.map(|events| events.into_iter().collect()));
|
||||
|
||||
let state = self.state.lock().await;
|
||||
for target in state.symlink_paths_by_target.keys() {
|
||||
if !target.starts_with(root_abs_path.as_path()) {
|
||||
self.watcher.add(target).log_err();
|
||||
}
|
||||
}
|
||||
for repo in state.snapshot.git_repositories.values() {
|
||||
if !repo
|
||||
.common_dir_abs_path
|
||||
.starts_with(root_abs_path.as_path())
|
||||
{
|
||||
self.watcher.add(&repo.common_dir_abs_path).log_err();
|
||||
}
|
||||
if !repo
|
||||
.repository_dir_abs_path
|
||||
.starts_with(root_abs_path.as_path())
|
||||
{
|
||||
self.watcher.add(&repo.repository_dir_abs_path).log_err();
|
||||
}
|
||||
}
|
||||
drop(state);
|
||||
}
|
||||
|
||||
// Process any any FS events that occurred while performing the initial scan.
|
||||
// For these events, update events cannot be as precise, because we didn't
|
||||
// have the previous state loaded yet.
|
||||
|
|
|
|||
|
|
@ -4237,3 +4237,170 @@ async fn test_remote_worktree_with_git_emits_root_repo_event_when_repo_info_arri
|
|||
"should fire exactly once, not duplicate"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deferred_watch_repository_above_root(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor);
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
".git": {},
|
||||
"subproject": {
|
||||
"a.txt": "A"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let worktree = Worktree::local(
|
||||
path!("/root/subproject").as_ref(),
|
||||
true,
|
||||
fs.clone(),
|
||||
Arc::default(),
|
||||
true,
|
||||
WorktreeId::from_proto(0),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
worktree
|
||||
.update(cx, |worktree, _| {
|
||||
worktree.as_local().unwrap().scan_complete()
|
||||
})
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree.as_local_mut().unwrap().set_defer_watch(true, cx);
|
||||
});
|
||||
worktree
|
||||
.update(cx, |worktree, _| {
|
||||
worktree.as_local().unwrap().scan_complete()
|
||||
})
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
|
||||
let repos = worktree.update(cx, |worktree, _| {
|
||||
worktree.as_local().unwrap().repositories()
|
||||
});
|
||||
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deferred_watch_symlinks_pointing_outside(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"dir1": {
|
||||
"deps": {},
|
||||
"src": {
|
||||
"a.rs": "",
|
||||
},
|
||||
},
|
||||
"dir2": {
|
||||
"src": {
|
||||
"c.rs": "",
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.create_symlink("/root/dir1/deps/dep-dir2".as_ref(), "../../dir2".into())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new("/root/dir1"),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
true,
|
||||
WorktreeId::from_proto(0),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
|
||||
tree.update(cx, |tree, cx| {
|
||||
tree.as_local_mut().unwrap().set_defer_watch(true, cx);
|
||||
});
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true, 0)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_external))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(rel_path(""), false),
|
||||
(rel_path("deps"), false),
|
||||
(rel_path("deps/dep-dir2"), true),
|
||||
(rel_path("src"), false),
|
||||
(rel_path("src/a.rs"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![rel_path("deps/dep-dir2").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true, 0)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_external))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(rel_path(""), false),
|
||||
(rel_path("deps"), false),
|
||||
(rel_path("deps/dep-dir2"), true),
|
||||
(rel_path("deps/dep-dir2/src"), true),
|
||||
(rel_path("src"), false),
|
||||
(rel_path("src/a.rs"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![rel_path("deps/dep-dir2/src").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert!(
|
||||
tree.entry_for_path(rel_path("deps/dep-dir2/src/c.rs"))
|
||||
.is_some()
|
||||
);
|
||||
});
|
||||
|
||||
fs.insert_file(Path::new("/root/dir2/src/new.rs"), b"".to_vec())
|
||||
.await;
|
||||
|
||||
wait_for_condition(cx, |cx| {
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.entry_for_path(rel_path("deps/dep-dir2/src/new.rs"))
|
||||
.is_some()
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue