Start on refactoring BackgroundScanner to be generic over fs

This commit is contained in:
Antonio Scandurra 2021-07-09 14:20:42 +02:00
parent 2fa63a3a50
commit d5361299ad

View file

@ -22,6 +22,7 @@ use gpui::{
use lazy_static::lazy_static;
use parking_lot::Mutex;
use postage::{
broadcast,
prelude::{Sink, Stream},
watch,
};
@ -153,7 +154,7 @@ struct InMemoryEntry {
struct InMemoryFsState {
entries: BTreeMap<PathBuf, InMemoryEntry>,
next_inode: u64,
events_tx: watch::Sender<()>,
events_tx: broadcast::Sender<fsevent::Event>,
}
impl InMemoryFsState {
@ -168,16 +169,26 @@ impl InMemoryFsState {
Err(anyhow!("invalid "))
}
}
async fn emit_event(&mut self, path: &Path) {
let _ = self
.events_tx
.send(fsevent::Event {
event_id: 0,
flags: fsevent::StreamFlags::empty(),
path: path.to_path_buf(),
})
.await;
}
}
pub struct InMemoryFs {
state: RwLock<InMemoryFsState>,
events_rx: watch::Receiver<()>,
}
impl InMemoryFs {
pub fn new() -> Self {
let (events_tx, events_rx) = watch::channel();
let (events_tx, _) = broadcast::channel(2048);
let mut entries = BTreeMap::new();
entries.insert(
Path::new("/").to_path_buf(),
@ -195,7 +206,6 @@ impl InMemoryFs {
next_inode: 1,
events_tx,
}),
events_rx,
}
}
@ -215,8 +225,33 @@ impl InMemoryFs {
content: None,
},
);
state.emit_event(path).await;
Ok(())
}
pub async fn remove(&self, path: &Path) -> Result<()> {
let mut state = self.state.write().await;
state.validate_path(path)?;
let mut paths = Vec::new();
state.entries.retain(|path, _| {
if path.starts_with(path) {
paths.push(path.to_path_buf());
false
} else {
true
}
});
for path in paths {
state.emit_event(&path).await;
}
Ok(())
}
pub async fn events(&self) -> broadcast::Receiver<fsevent::Event> {
self.state.read().await.events_tx.subscribe()
}
}
#[async_trait::async_trait]
@ -267,6 +302,7 @@ impl Fs for InMemoryFs {
} else {
entry.content = Some(text.chunks().collect());
entry.mtime = SystemTime::now();
state.emit_event(path).await;
Ok(())
}
} else {
@ -282,6 +318,7 @@ impl Fs for InMemoryFs {
content: Some(text.chunks().collect()),
},
);
state.emit_event(path).await;
Ok(())
}
}
@ -291,7 +328,7 @@ impl Fs for InMemoryFs {
enum ScanState {
Idle,
Scanning,
Err(Arc<io::Error>),
Err(Arc<anyhow::Error>),
}
pub enum Worktree {
@ -331,7 +368,7 @@ impl Worktree {
cx: &mut ModelContext<Worktree>,
) -> Self {
let fs = Arc::new(OsFs);
let (mut tree, scan_states_tx) = LocalWorktree::new(path, languages, fs, cx);
let (mut tree, scan_states_tx) = LocalWorktree::new(path, languages, fs.clone(), cx);
let (event_stream, event_stream_handle) = fsevent::EventStream::new(
&[tree.snapshot.abs_path.as_ref()],
Duration::from_millis(100),
@ -339,7 +376,7 @@ impl Worktree {
let background_snapshot = tree.background_snapshot.clone();
let id = tree.id;
std::thread::spawn(move || {
let scanner = BackgroundScanner::new(background_snapshot, scan_states_tx, id);
let scanner = BackgroundScanner::new(fs, background_snapshot, scan_states_tx, id);
scanner.run(event_stream);
});
tree._event_stream_handle = Some(event_stream_handle);
@ -356,7 +393,14 @@ impl Worktree {
let (tree, scan_states_tx) = LocalWorktree::new(path, languages, fs.clone(), cx);
let background_snapshot = tree.background_snapshot.clone();
let id = tree.id;
cx.background().spawn(async move {}).detach();
let fs = fs.clone();
cx.background()
.spawn(async move {
let events_rx = fs.events().await;
let scanner = BackgroundScanner::new(fs, background_snapshot, scan_states_tx, id);
scanner.run_test(events_rx).await;
})
.detach();
Worktree::Local(tree)
}
@ -1933,14 +1977,21 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
}
struct BackgroundScanner {
fs: Arc<dyn Fs>,
snapshot: Arc<Mutex<Snapshot>>,
notify: Sender<ScanState>,
thread_pool: scoped_pool::Pool,
}
impl BackgroundScanner {
fn new(snapshot: Arc<Mutex<Snapshot>>, notify: Sender<ScanState>, worktree_id: usize) -> Self {
fn new(
fs: Arc<dyn Fs>,
snapshot: Arc<Mutex<Snapshot>>,
notify: Sender<ScanState>,
worktree_id: usize,
) -> Self {
Self {
fs,
snapshot,
notify,
thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
@ -1960,7 +2011,7 @@ impl BackgroundScanner {
return;
}
if let Err(err) = self.scan_dirs() {
if let Err(err) = smol::block_on(self.scan_dirs()) {
if smol::block_on(self.notify.send(ScanState::Err(Arc::new(err)))).is_err() {
return;
}
@ -1975,7 +2026,7 @@ impl BackgroundScanner {
return false;
}
if !self.process_events(events) {
if !smol::block_on(self.process_events(events)) {
return false;
}
@ -1987,46 +2038,82 @@ impl BackgroundScanner {
});
}
fn scan_dirs(&mut self) -> io::Result<()> {
self.snapshot.lock().scan_id += 1;
#[cfg(any(test, feature = "test-support"))]
async fn run_test(mut self, mut events_rx: broadcast::Receiver<fsevent::Event>) {
if self.notify.send(ScanState::Scanning).await.is_err() {
return;
}
if let Err(err) = self.scan_dirs().await {
if self
.notify
.send(ScanState::Err(Arc::new(err)))
.await
.is_err()
{
return;
}
}
if self.notify.send(ScanState::Idle).await.is_err() {
return;
}
while let Some(event) = events_rx.recv().await {
let mut events = vec![event];
while let Ok(event) = events_rx.try_recv() {
events.push(event);
}
if self.notify.send(ScanState::Scanning).await.is_err() {
break;
}
if self.process_events(events).await {
break;
}
if self.notify.send(ScanState::Idle).await.is_err() {
break;
}
}
}
async fn scan_dirs(&mut self) -> Result<()> {
let next_entry_id;
{
let mut snapshot = self.snapshot.lock();
snapshot.scan_id += 1;
next_entry_id = snapshot.next_entry_id.clone();
}
let path: Arc<Path> = Arc::from(Path::new(""));
let abs_path = self.abs_path();
let metadata = fs::metadata(&abs_path)?;
let inode = metadata.ino();
let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
let is_dir = metadata.file_type().is_dir();
let mtime = metadata.modified()?;
// After determining whether the root entry is a file or a directory, populate the
// snapshot's "root name", which will be used for the purpose of fuzzy matching.
let mut root_name = abs_path
.file_name()
.map_or(String::new(), |f| f.to_string_lossy().to_string());
let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
let entry = self
.fs
.entry(root_char_bag, &next_entry_id, path.clone(), &abs_path)
.await?
.ok_or_else(|| anyhow!("root entry does not exist"))?;
let is_dir = entry.is_dir();
if is_dir {
root_name.push('/');
}
let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
let next_entry_id;
{
let mut snapshot = self.snapshot.lock();
snapshot.root_name = root_name;
snapshot.root_char_bag = root_char_bag;
next_entry_id = snapshot.next_entry_id.clone();
}
self.snapshot.lock().insert_entry(entry);
if is_dir {
self.snapshot.lock().insert_entry(Entry {
id: next_entry_id.fetch_add(1, SeqCst),
kind: EntryKind::PendingDir,
path: path.clone(),
inode,
mtime,
is_symlink,
is_ignored: false,
});
let (tx, rx) = crossbeam_channel::unbounded();
tx.send(ScanJob {
abs_path: abs_path.to_path_buf(),
@ -2041,31 +2128,23 @@ impl BackgroundScanner {
for _ in 0..self.thread_pool.thread_count() {
pool.execute(|| {
while let Ok(job) = rx.recv() {
if let Err(err) =
self.scan_dir(root_char_bag, next_entry_id.clone(), &job)
{
if let Err(err) = smol::block_on(self.scan_dir(
root_char_bag,
next_entry_id.clone(),
&job,
)) {
log::error!("error scanning {:?}: {}", job.abs_path, err);
}
}
});
}
});
} else {
self.snapshot.lock().insert_entry(Entry {
id: next_entry_id.fetch_add(1, SeqCst),
kind: EntryKind::File(char_bag_for_path(root_char_bag, &path)),
path,
inode,
mtime,
is_symlink,
is_ignored: false,
});
}
Ok(())
}
fn scan_dir(
async fn scan_dir(
&self,
root_char_bag: CharBag,
next_entry_id: Arc<AtomicUsize>,
@ -2164,7 +2243,7 @@ impl BackgroundScanner {
Ok(())
}
fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
async fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
let mut snapshot = self.snapshot();
snapshot.scan_id += 1;
@ -2207,12 +2286,16 @@ impl BackgroundScanner {
}
};
match smol::block_on(OsFs.entry(
snapshot.root_char_bag,
&next_entry_id,
path.clone(),
&event.path,
)) {
match self
.fs
.entry(
snapshot.root_char_bag,
&next_entry_id,
path.clone(),
&event.path,
)
.await
{
Ok(Some(mut fs_entry)) => {
let is_dir = fs_entry.is_dir();
let ignore_stack = snapshot.ignore_stack_for_path(&path, is_dir);
@ -2245,8 +2328,11 @@ impl BackgroundScanner {
for _ in 0..self.thread_pool.thread_count() {
pool.execute(|| {
while let Ok(job) = scan_queue_rx.recv() {
if let Err(err) = self.scan_dir(root_char_bag, next_entry_id.clone(), &job)
{
if let Err(err) = smol::block_on(self.scan_dir(
root_char_bag,
next_entry_id.clone(),
&job,
)) {
log::error!("error scanning {:?}: {}", job.abs_path, err);
}
}
@ -3061,6 +3147,7 @@ mod tests {
let (notify_tx, _notify_rx) = smol::channel::unbounded();
let mut scanner = BackgroundScanner::new(
Arc::new(OsFs),
Arc::new(Mutex::new(Snapshot {
id: 0,
scan_id: 0,
@ -3076,7 +3163,7 @@ mod tests {
notify_tx,
0,
);
scanner.scan_dirs().unwrap();
smol::block_on(scanner.scan_dirs()).unwrap();
scanner.snapshot().check_invariants();
let mut events = Vec::new();
@ -3086,7 +3173,7 @@ mod tests {
let len = rng.gen_range(0..=events.len());
let to_deliver = events.drain(0..len).collect::<Vec<_>>();
log::info!("Delivering events: {:#?}", to_deliver);
scanner.process_events(to_deliver);
smol::block_on(scanner.process_events(to_deliver));
scanner.snapshot().check_invariants();
} else {
events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
@ -3094,11 +3181,12 @@ mod tests {
}
}
log::info!("Quiescing: {:#?}", events);
scanner.process_events(events);
smol::block_on(scanner.process_events(events));
scanner.snapshot().check_invariants();
let (notify_tx, _notify_rx) = smol::channel::unbounded();
let mut new_scanner = BackgroundScanner::new(
scanner.fs.clone(),
Arc::new(Mutex::new(Snapshot {
id: 0,
scan_id: 0,
@ -3114,7 +3202,7 @@ mod tests {
notify_tx,
1,
);
new_scanner.scan_dirs().unwrap();
smol::block_on(new_scanner.scan_dirs()).unwrap();
assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
}
}