Only mutate background snapshot in the background scanner

This commit is contained in:
Max Brunsfeld 2023-03-21 11:21:58 -07:00
parent d742c758bc
commit b10b0dbd75

View File

@ -9,7 +9,7 @@ use fs::LineEnding;
use fs::{repository::GitRepository, Fs}; use fs::{repository::GitRepository, Fs};
use futures::{ use futures::{
channel::{ channel::{
mpsc::{self, UnboundedSender}, mpsc::{self, UnboundedReceiver, UnboundedSender},
oneshot, oneshot,
}, },
Stream, StreamExt, Stream, StreamExt,
@ -29,6 +29,7 @@ use language::{
Buffer, DiagnosticEntry, PointUtf16, Rope, RopeFingerprint, Unclipped, Buffer, DiagnosticEntry, PointUtf16, Rope, RopeFingerprint, Unclipped,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::barrier;
use postage::{ use postage::{
prelude::{Sink as _, Stream as _}, prelude::{Sink as _, Stream as _},
watch, watch,
@ -55,7 +56,6 @@ use util::{ResultExt, TryFutureExt};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize); pub struct WorktreeId(usize);
#[allow(clippy::large_enum_variant)]
pub enum Worktree { pub enum Worktree {
Local(LocalWorktree), Local(LocalWorktree),
Remote(RemoteWorktree), Remote(RemoteWorktree),
@ -63,7 +63,7 @@ pub enum Worktree {
pub struct LocalWorktree { pub struct LocalWorktree {
snapshot: LocalSnapshot, snapshot: LocalSnapshot,
background_snapshot: Arc<Mutex<LocalSnapshot>>, path_changes_tx: mpsc::UnboundedSender<(Vec<PathBuf>, barrier::Sender)>,
is_scanning: (watch::Sender<bool>, watch::Receiver<bool>), is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
_background_scanner_task: Task<()>, _background_scanner_task: Task<()>,
share: Option<ShareState>, share: Option<ShareState>,
@ -156,14 +156,17 @@ impl DerefMut for LocalSnapshot {
} }
} }
#[derive(Clone, Debug)]
enum ScanState { enum ScanState {
/// The worktree is performing its initial scan of the filesystem. /// The worktree is performing its initial scan of the filesystem.
Initializing(LocalSnapshot), Initializing(LocalSnapshot),
Initialized(LocalSnapshot), Initialized(LocalSnapshot),
/// The worktree is updating in response to filesystem events. /// The worktree is updating in response to filesystem events.
Updating, Updating,
Updated(LocalSnapshot, HashMap<Arc<Path>, PathChange>), Updated {
snapshot: LocalSnapshot,
changes: HashMap<Arc<Path>, PathChange>,
barrier: Option<barrier::Sender>,
},
Err(Arc<anyhow::Error>), Err(Arc<anyhow::Error>),
} }
@ -234,8 +237,8 @@ impl Worktree {
); );
} }
let (path_changes_tx, path_changes_rx) = mpsc::unbounded();
let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded(); let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
cx.spawn_weak(|this, mut cx| async move { cx.spawn_weak(|this, mut cx| async move {
while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade(&cx)) { while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade(&cx)) {
@ -250,21 +253,21 @@ impl Worktree {
let background_scanner_task = cx.background().spawn({ let background_scanner_task = cx.background().spawn({
let fs = fs.clone(); let fs = fs.clone();
let background_snapshot = background_snapshot.clone(); let snapshot = snapshot.clone();
let background = cx.background().clone(); let background = cx.background().clone();
async move { async move {
let events = fs.watch(&abs_path, Duration::from_millis(100)).await; let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
BackgroundScanner::new(background_snapshot, scan_states_tx, fs, background) BackgroundScanner::new(snapshot, scan_states_tx, fs, background)
.run(events) .run(events, path_changes_rx)
.await; .await;
} }
}); });
Worktree::Local(LocalWorktree { Worktree::Local(LocalWorktree {
snapshot, snapshot,
background_snapshot,
is_scanning: watch::channel_with(true), is_scanning: watch::channel_with(true),
share: None, share: None,
path_changes_tx,
_background_scanner_task: background_scanner_task, _background_scanner_task: background_scanner_task,
diagnostics: Default::default(), diagnostics: Default::default(),
diagnostic_summaries: Default::default(), diagnostic_summaries: Default::default(),
@ -546,10 +549,15 @@ impl LocalWorktree {
ScanState::Updating => { ScanState::Updating => {
*self.is_scanning.0.borrow_mut() = true; *self.is_scanning.0.borrow_mut() = true;
} }
ScanState::Updated(new_snapshot, changes) => { ScanState::Updated {
snapshot: new_snapshot,
changes,
barrier,
} => {
*self.is_scanning.0.borrow_mut() = false; *self.is_scanning.0.borrow_mut() = false;
cx.emit(Event::UpdatedEntries(changes)); cx.emit(Event::UpdatedEntries(changes));
self.set_snapshot(new_snapshot, cx); self.set_snapshot(new_snapshot, cx);
drop(barrier);
} }
ScanState::Err(error) => { ScanState::Err(error) => {
*self.is_scanning.0.borrow_mut() = false; *self.is_scanning.0.borrow_mut() = false;
@ -660,9 +668,7 @@ impl LocalWorktree {
// Eagerly populate the snapshot with an updated entry for the loaded file // Eagerly populate the snapshot with an updated entry for the loaded file
let entry = this let entry = this
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.as_local() this.as_local().unwrap().refresh_entry(path, None, cx)
.unwrap()
.refresh_entry(path, abs_path, None, cx)
}) })
.await?; .await?;
@ -780,10 +786,13 @@ impl LocalWorktree {
cx: &mut ModelContext<Worktree>, cx: &mut ModelContext<Worktree>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
let entry = self.entry_for_id(entry_id)?.clone(); let entry = self.entry_for_id(entry_id)?.clone();
let abs_path = self.absolutize(&entry.path); let path = entry.path.clone();
let abs_path = self.absolutize(&path);
let (tx, mut rx) = barrier::channel();
let delete = cx.background().spawn({ let delete = cx.background().spawn({
let abs_path = abs_path.clone();
let fs = self.fs.clone(); let fs = self.fs.clone();
let abs_path = abs_path;
async move { async move {
if entry.is_file() { if entry.is_file() {
fs.remove_file(&abs_path, Default::default()).await fs.remove_file(&abs_path, Default::default()).await
@ -802,17 +811,14 @@ impl LocalWorktree {
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
delete.await?; delete.await?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, _| {
let this = this.as_local_mut().unwrap(); this.as_local_mut()
.unwrap()
this.background_snapshot.lock().delete_entry(entry_id); .path_changes_tx
.unbounded_send((vec![abs_path], tx))
if let Some(path) = this.snapshot.delete_entry(entry_id) { .unwrap();
cx.emit(Event::UpdatedEntries(
[(path, PathChange::Removed)].into_iter().collect(),
));
}
}); });
rx.recv().await;
Ok(()) Ok(())
})) }))
} }
@ -826,29 +832,21 @@ impl LocalWorktree {
let old_path = self.entry_for_id(entry_id)?.path.clone(); let old_path = self.entry_for_id(entry_id)?.path.clone();
let new_path = new_path.into(); let new_path = new_path.into();
let abs_old_path = self.absolutize(&old_path); let abs_old_path = self.absolutize(&old_path);
let abs_new_path = self.absolutize(new_path.as_ref()); let abs_new_path = self.absolutize(&new_path);
let rename = cx.background().spawn({
let fs = self.fs.clone(); let fs = self.fs.clone();
let abs_new_path = abs_new_path.clone(); let rename = cx.background().spawn(async move {
async move {
fs.rename(&abs_old_path, &abs_new_path, Default::default()) fs.rename(&abs_old_path, &abs_new_path, Default::default())
.await .await
}
}); });
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
rename.await?; rename.await?;
let entry = this this.update(&mut cx, |this, cx| {
.update(&mut cx, |this, cx| { this.as_local_mut()
this.as_local_mut().unwrap().refresh_entry( .unwrap()
new_path.clone(), .refresh_entry(new_path.clone(), Some(old_path), cx)
abs_new_path,
Some(old_path),
cx,
)
}) })
.await?; .await
Ok(entry)
})) }))
} }
@ -862,10 +860,8 @@ impl LocalWorktree {
let new_path = new_path.into(); let new_path = new_path.into();
let abs_old_path = self.absolutize(&old_path); let abs_old_path = self.absolutize(&old_path);
let abs_new_path = self.absolutize(&new_path); let abs_new_path = self.absolutize(&new_path);
let copy = cx.background().spawn({
let fs = self.fs.clone(); let fs = self.fs.clone();
let abs_new_path = abs_new_path.clone(); let copy = cx.background().spawn(async move {
async move {
copy_recursive( copy_recursive(
fs.as_ref(), fs.as_ref(),
&abs_old_path, &abs_old_path,
@ -873,22 +869,16 @@ impl LocalWorktree {
Default::default(), Default::default(),
) )
.await .await
}
}); });
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
copy.await?; copy.await?;
let entry = this this.update(&mut cx, |this, cx| {
.update(&mut cx, |this, cx| { this.as_local_mut()
this.as_local_mut().unwrap().refresh_entry( .unwrap()
new_path.clone(), .refresh_entry(new_path.clone(), None, cx)
abs_new_path,
None,
cx,
)
}) })
.await?; .await
Ok(entry)
})) }))
} }
@ -900,89 +890,50 @@ impl LocalWorktree {
) -> Task<Result<Entry>> { ) -> Task<Result<Entry>> {
let path = path.into(); let path = path.into();
let abs_path = self.absolutize(&path); let abs_path = self.absolutize(&path);
let write = cx.background().spawn({
let fs = self.fs.clone(); let fs = self.fs.clone();
let abs_path = abs_path.clone(); let write = cx.background().spawn(async move {
async move {
if let Some((text, line_ending)) = text_if_file { if let Some((text, line_ending)) = text_if_file {
fs.save(&abs_path, &text, line_ending).await fs.save(&abs_path, &text, line_ending).await
} else { } else {
fs.create_dir(&abs_path).await fs.create_dir(&abs_path).await
} }
}
}); });
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
write.await?; write.await?;
let entry = this this.update(&mut cx, |this, cx| {
.update(&mut cx, |this, cx| { this.as_local_mut().unwrap().refresh_entry(path, None, cx)
this.as_local_mut()
.unwrap()
.refresh_entry(path, abs_path, None, cx)
}) })
.await?; .await
Ok(entry)
}) })
} }
fn refresh_entry( fn refresh_entry(
&self, &self,
path: Arc<Path>, path: Arc<Path>,
abs_path: PathBuf,
old_path: Option<Arc<Path>>, old_path: Option<Arc<Path>>,
cx: &mut ModelContext<Worktree>, cx: &mut ModelContext<Worktree>,
) -> Task<Result<Entry>> { ) -> Task<Result<Entry>> {
let fs = self.fs.clone(); let fs = self.fs.clone();
let root_char_bag = self.snapshot.root_char_bag; let abs_path = self.abs_path.clone();
let next_entry_id = self.snapshot.next_entry_id.clone(); let path_changes_tx = self.path_changes_tx.clone();
cx.spawn_weak(|this, mut cx| async move { cx.spawn_weak(move |this, mut cx| async move {
let metadata = fs let abs_path = fs.canonicalize(&abs_path).await?;
.metadata(&abs_path) let paths = if let Some(old_path) = old_path {
.await? vec![abs_path.join(&path), abs_path.join(&old_path)]
.ok_or_else(|| anyhow!("could not read saved file metadata"))?;
let this = this
.upgrade(&cx)
.ok_or_else(|| anyhow!("worktree was dropped"))?;
this.update(&mut cx, |this, cx| {
let this = this.as_local_mut().unwrap();
let mut entry = Entry::new(path, &metadata, &next_entry_id, root_char_bag);
entry.is_ignored = this
.snapshot
.ignore_stack_for_abs_path(&abs_path, entry.is_dir())
.is_abs_path_ignored(&abs_path, entry.is_dir());
{
let mut snapshot = this.background_snapshot.lock();
snapshot.scan_started();
if let Some(old_path) = &old_path {
snapshot.remove_path(old_path);
}
snapshot.insert_entry(entry.clone(), fs.as_ref());
snapshot.scan_completed();
}
let mut changes = HashMap::default();
this.snapshot.scan_started();
if let Some(old_path) = &old_path {
this.snapshot.remove_path(old_path);
changes.insert(old_path.clone(), PathChange::Removed);
}
let exists = this.snapshot.entry_for_path(&entry.path).is_some();
let inserted_entry = this.snapshot.insert_entry(entry, fs.as_ref());
changes.insert(
inserted_entry.path.clone(),
if exists {
PathChange::Updated
} else { } else {
PathChange::Added vec![abs_path.join(&path)]
}, };
);
this.snapshot.scan_completed();
eprintln!("refreshed {:?}", changes); let (tx, mut rx) = barrier::channel();
cx.emit(Event::UpdatedEntries(changes)); path_changes_tx.unbounded_send((paths, tx)).unwrap();
Ok(inserted_entry) rx.recv().await;
this.upgrade(&cx)
.ok_or_else(|| anyhow!("worktree was dropped"))?
.update(&mut cx, |this, _| {
this.entry_for_path(path)
.cloned()
.ok_or_else(|| anyhow!("failed to read path after update"))
}) })
}) })
} }
@ -2188,14 +2139,14 @@ struct BackgroundScanner {
impl BackgroundScanner { impl BackgroundScanner {
fn new( fn new(
snapshot: Arc<Mutex<LocalSnapshot>>, snapshot: LocalSnapshot,
notify: UnboundedSender<ScanState>, notify: UnboundedSender<ScanState>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
executor: Arc<executor::Background>, executor: Arc<executor::Background>,
) -> Self { ) -> Self {
Self { Self {
fs, fs,
snapshot, snapshot: Arc::new(Mutex::new(snapshot)),
notify, notify,
executor, executor,
changes: Default::default(), changes: Default::default(),
@ -2206,7 +2157,13 @@ impl BackgroundScanner {
self.snapshot.lock().abs_path.clone() self.snapshot.lock().abs_path.clone()
} }
async fn run(mut self, events_rx: impl Stream<Item = Vec<fsevent::Event>>) { async fn run(
mut self,
events_rx: impl Stream<Item = Vec<fsevent::Event>>,
mut changed_paths: UnboundedReceiver<(Vec<PathBuf>, barrier::Sender)>,
) {
use futures::{select_biased, FutureExt as _};
// While performing the initial scan, send a new snapshot to the main // While performing the initial scan, send a new snapshot to the main
// thread on a recurring interval. // thread on a recurring interval.
let initializing_task = self.executor.spawn({ let initializing_task = self.executor.spawn({
@ -2260,7 +2217,7 @@ impl BackgroundScanner {
// Process any events that occurred while performing the initial scan. These // Process any events that occurred while performing the initial scan. These
// events can't be reported as precisely, because there is no snapshot of the // events can't be reported as precisely, because there is no snapshot of the
// worktree before they occurred. // worktree before they occurred.
if let Some(mut events) = events_rx.next().await { if let Poll::Ready(Some(mut events)) = futures::poll!(events_rx.next()) {
while let Poll::Ready(Some(additional_events)) = futures::poll!(events_rx.next()) { while let Poll::Ready(Some(additional_events)) = futures::poll!(events_rx.next()) {
events.extend(additional_events); events.extend(additional_events);
} }
@ -2272,10 +2229,11 @@ impl BackgroundScanner {
} }
if self if self
.notify .notify
.unbounded_send(ScanState::Updated( .unbounded_send(ScanState::Updated {
self.snapshot.lock().clone(), snapshot: self.snapshot.lock().clone(),
mem::take(&mut self.changes), changes: mem::take(&mut self.changes),
)) barrier: None,
})
.is_err() .is_err()
{ {
return; return;
@ -2283,10 +2241,29 @@ impl BackgroundScanner {
} }
// Continue processing events until the worktree is dropped. // Continue processing events until the worktree is dropped.
while let Some(mut events) = events_rx.next().await { loop {
while let Poll::Ready(Some(additional_events)) = futures::poll!(events_rx.next()) { let events;
events.extend(additional_events); let barrier;
select_biased! {
request = changed_paths.next().fuse() => {
let Some((paths, b)) = request else { break; };
events = paths
.into_iter()
.map(|path| fsevent::Event {
path,
event_id: 0,
flags: fsevent::StreamFlags::NONE
})
.collect::<Vec<_>>();
barrier = Some(b);
} }
e = events_rx.next().fuse() => {
let Some(e) = e else { break; };
events = e;
barrier = None;
}
}
if self.notify.unbounded_send(ScanState::Updating).is_err() { if self.notify.unbounded_send(ScanState::Updating).is_err() {
return; return;
} }
@ -2295,10 +2272,11 @@ impl BackgroundScanner {
} }
if self if self
.notify .notify
.unbounded_send(ScanState::Updated( .unbounded_send(ScanState::Updated {
self.snapshot.lock().clone(), snapshot: self.snapshot.lock().clone(),
mem::take(&mut self.changes), changes: mem::take(&mut self.changes),
)) barrier,
})
.is_err() .is_err()
{ {
return; return;
@ -3132,7 +3110,7 @@ mod tests {
let tree = Worktree::local( let tree = Worktree::local(
client, client,
Arc::from(Path::new("/root")), Path::new("/root"),
true, true,
fs, fs,
Default::default(), Default::default(),
@ -3193,7 +3171,7 @@ mod tests {
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let tree = Worktree::local( let tree = Worktree::local(
client, client,
Arc::from(Path::new("/root")), Path::new("/root"),
true, true,
fs.clone(), fs.clone(),
Default::default(), Default::default(),