rust-analyzer/crates/vfs-notify/src/lib.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

358 lines
14 KiB
Rust
Raw Normal View History

2020-06-11 09:04:09 +00:00
//! An implementation of `loader::Handle`, based on `walkdir` and `notify`.
//!
//! The file watching bits here are untested and quite probably buggy. For this
//! reason, by default we don't watch files and rely on editor's file watching
//! capabilities.
//!
//! Hopefully, one day a reliable file watching/walking crate appears on
//! crates.io, and we can reduce this to trivial glue code.
use std::{
fs,
path::{Component, Path},
sync::atomic::AtomicUsize,
};
2020-06-11 09:04:09 +00:00
use crossbeam_channel::{select, unbounded, Receiver, Sender};
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rayon::iter::{IndexedParallelIterator as _, IntoParallelIterator as _, ParallelIterator};
2024-08-05 13:56:23 +00:00
use rustc_hash::FxHashSet;
use vfs::loader::{self, LoadingProgress};
2020-06-11 09:04:09 +00:00
use walkdir::WalkDir;
#[derive(Debug)]
2020-06-25 06:59:55 +00:00
pub struct NotifyHandle {
2020-06-11 09:04:09 +00:00
// Relative order of fields below is significant.
2020-06-28 20:35:18 +00:00
sender: Sender<Message>,
_thread: stdx::thread::JoinHandle,
2020-06-11 09:04:09 +00:00
}
#[derive(Debug)]
enum Message {
Config(loader::Config),
Invalidate(AbsPathBuf),
}
2020-06-25 06:59:55 +00:00
impl loader::Handle for NotifyHandle {
fn spawn(sender: loader::Sender) -> NotifyHandle {
let actor = NotifyActor::new(sender);
2020-06-11 09:04:09 +00:00
let (sender, receiver) = unbounded::<Message>();
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
.name("VfsLoader".to_owned())
.spawn(move || actor.run(receiver))
.expect("failed to spawn thread");
2021-09-15 18:22:06 +00:00
NotifyHandle { sender, _thread: thread }
2020-06-11 09:04:09 +00:00
}
2020-06-11 09:04:09 +00:00
fn set_config(&mut self, config: loader::Config) {
self.sender.send(Message::Config(config)).unwrap();
2020-06-11 09:04:09 +00:00
}
2020-06-11 09:04:09 +00:00
fn invalidate(&mut self, path: AbsPathBuf) {
self.sender.send(Message::Invalidate(path)).unwrap();
}
2020-06-24 13:52:07 +00:00
fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>> {
2020-06-11 09:04:09 +00:00
read(path)
}
}
type NotifyEvent = notify::Result<notify::Event>;
2020-06-25 06:59:55 +00:00
struct NotifyActor {
sender: loader::Sender,
2024-08-05 13:56:23 +00:00
watched_file_entries: FxHashSet<AbsPathBuf>,
watched_dir_entries: Vec<loader::Directories>,
2020-07-06 07:28:17 +00:00
// Drop order is significant.
watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
2020-06-11 09:04:09 +00:00
}
#[derive(Debug)]
enum Event {
Message(Message),
NotifyEvent(NotifyEvent),
}
2020-06-25 06:59:55 +00:00
impl NotifyActor {
fn new(sender: loader::Sender) -> NotifyActor {
2024-08-05 13:56:23 +00:00
NotifyActor {
sender,
watched_dir_entries: Vec::new(),
watched_file_entries: FxHashSet::default(),
watcher: None,
}
2020-06-11 09:04:09 +00:00
}
2020-06-25 15:14:11 +00:00
fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
let Some((_, watcher_receiver)) = &self.watcher else {
return receiver.recv().ok().map(Event::Message);
};
2020-06-25 15:14:11 +00:00
select! {
recv(receiver) -> it => it.ok().map(Event::Message),
recv(watcher_receiver) -> it => Some(Event::NotifyEvent(it.unwrap())),
2020-06-25 15:14:11 +00:00
}
}
2020-06-25 11:47:22 +00:00
fn run(mut self, inbox: Receiver<Message>) {
while let Some(event) = self.next_event(&inbox) {
tracing::debug!(?event, "vfs-notify event");
2020-06-11 09:04:09 +00:00
match event {
Event::Message(msg) => match msg {
Message::Config(config) => {
2020-07-06 07:28:17 +00:00
self.watcher = None;
2020-07-10 21:39:25 +00:00
if !config.watch.is_empty() {
let (watcher_sender, watcher_receiver) = unbounded();
2022-08-19 18:37:05 +00:00
let watcher = log_notify_error(RecommendedWatcher::new(
move |event| {
2024-08-07 14:35:42 +00:00
// we don't care about the error. If sending fails that usually
// means we were dropped, so unwrapping will just add to the
// panic noise.
_ = watcher_sender.send(event);
2022-08-19 18:37:05 +00:00
},
Config::default(),
));
2020-07-10 21:39:25 +00:00
self.watcher = watcher.map(|it| (it, watcher_receiver));
}
2020-07-06 07:28:17 +00:00
let config_version = config.version;
2020-06-24 14:58:49 +00:00
let n_total = config.load.len();
2024-08-05 13:56:23 +00:00
self.watched_dir_entries.clear();
self.watched_file_entries.clear();
2024-08-12 08:55:04 +00:00
self.send(loader::Message::Progress {
n_total,
n_done: LoadingProgress::Started,
config_version,
dir: None,
});
2020-06-11 09:04:09 +00:00
let (entry_tx, entry_rx) = unbounded();
let (watch_tx, watch_rx) = unbounded();
let processed = AtomicUsize::new(0);
2024-08-11 12:58:50 +00:00
config.load.into_par_iter().enumerate().for_each(|(i, entry)| {
let do_watch = config.watch.contains(&i);
if do_watch {
_ = entry_tx.send(entry.clone());
2020-07-18 14:40:10 +00:00
}
let files = Self::load_entry(
|f| _ = watch_tx.send(f.to_owned()),
entry,
do_watch,
|file| {
2024-08-12 08:55:04 +00:00
self.send(loader::Message::Progress {
n_total,
n_done: LoadingProgress::Progress(
processed.load(std::sync::atomic::Ordering::Relaxed),
),
dir: Some(file),
config_version,
});
},
);
2024-08-12 08:55:04 +00:00
self.send(loader::Message::Loaded { files });
self.send(loader::Message::Progress {
n_total,
n_done: LoadingProgress::Progress(
processed.fetch_add(1, std::sync::atomic::Ordering::AcqRel) + 1,
),
config_version,
dir: None,
});
});
2024-08-11 12:58:50 +00:00
drop(watch_tx);
for path in watch_rx {
self.watch(&path);
}
2024-08-11 12:58:50 +00:00
drop(entry_tx);
for entry in entry_rx {
2024-08-05 13:56:23 +00:00
match entry {
loader::Entry::Files(files) => {
self.watched_file_entries.extend(files)
}
loader::Entry::Directories(dir) => {
self.watched_dir_entries.push(dir)
}
}
2020-06-11 09:04:09 +00:00
}
2024-08-11 12:58:50 +00:00
self.send(loader::Message::Progress {
n_total,
n_done: LoadingProgress::Finished,
config_version,
dir: None,
});
2020-06-11 09:04:09 +00:00
}
Message::Invalidate(path) => {
let contents = read(path.as_path());
let files = vec![(path, contents)];
self.send(loader::Message::Changed { files });
2020-06-11 09:04:09 +00:00
}
},
Event::NotifyEvent(event) => {
if let Some(event) = log_notify_error(event) {
if let EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) =
event.kind
{
let files = event
.paths
.into_iter()
.filter_map(|path| {
Some(
AbsPathBuf::try_from(
Utf8PathBuf::from_path_buf(path).ok()?,
)
.expect("path is absolute"),
)
})
2024-08-05 13:56:23 +00:00
.filter_map(|path| -> Option<(AbsPathBuf, Option<Vec<u8>>)> {
let meta = fs::metadata(&path).ok()?;
if meta.file_type().is_dir()
&& self
2024-08-05 13:56:23 +00:00
.watched_dir_entries
.iter()
2024-08-05 13:56:23 +00:00
.any(|dir| dir.contains_dir(&path))
{
self.watch(path.as_ref());
return None;
}
if !meta.file_type().is_file() {
return None;
}
2024-08-05 13:56:23 +00:00
if !(self.watched_file_entries.contains(&path)
|| self
.watched_dir_entries
.iter()
.any(|dir| dir.contains_file(&path)))
{
return None;
}
let contents = read(&path);
Some((path, contents))
})
.collect();
self.send(loader::Message::Changed { files });
}
2020-06-11 09:04:09 +00:00
}
}
}
}
}
2020-06-11 09:04:09 +00:00
fn load_entry(
mut watch: impl FnMut(&Path),
2020-06-11 09:04:09 +00:00
entry: loader::Entry,
do_watch: bool,
send_message: impl Fn(AbsPathBuf),
2020-06-11 09:04:09 +00:00
) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
match entry {
loader::Entry::Files(files) => files
.into_iter()
.map(|file| {
if do_watch {
watch(file.as_ref());
2020-06-11 09:04:09 +00:00
}
let contents = read(file.as_path());
(file, contents)
})
.collect::<Vec<_>>(),
2020-07-18 14:40:10 +00:00
loader::Entry::Directories(dirs) => {
let mut res = Vec::new();
2021-10-03 12:45:08 +00:00
for root in &dirs.include {
send_message(root.clone());
let walkdir =
WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
if !entry.file_type().is_dir() {
return true;
}
let path = entry.path();
if path_might_be_cyclic(path) {
return false;
}
root == path
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
});
2020-07-18 14:40:10 +00:00
let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
let depth = entry.depth();
2020-06-11 09:04:09 +00:00
let is_dir = entry.file_type().is_dir();
let is_file = entry.file_type().is_file();
let abs_path = AbsPathBuf::try_from(
Utf8PathBuf::from_path_buf(entry.into_path()).ok()?,
)
.ok()?;
if depth < 2 && is_dir {
send_message(abs_path.clone());
}
if is_dir && do_watch {
watch(abs_path.as_ref());
2020-06-11 09:04:09 +00:00
}
2020-07-18 14:40:10 +00:00
if !is_file {
return None;
}
let ext = abs_path.extension().unwrap_or_default();
if dirs.extensions.iter().all(|it| it.as_str() != ext) {
return None;
2020-06-11 09:04:09 +00:00
}
2020-07-18 14:40:10 +00:00
Some(abs_path)
2020-06-11 09:04:09 +00:00
});
2020-07-18 14:40:10 +00:00
res.extend(files.map(|file| {
2020-06-11 09:04:09 +00:00
let contents = read(file.as_path());
(file, contents)
2020-07-18 14:40:10 +00:00
}));
}
res
2020-06-11 09:04:09 +00:00
}
}
}
fn watch(&mut self, path: &Path) {
2020-07-06 07:28:17 +00:00
if let Some((watcher, _)) = &mut self.watcher {
log_notify_error(watcher.watch(path, RecursiveMode::NonRecursive));
2020-06-11 09:04:09 +00:00
}
}
2024-08-12 08:44:00 +00:00
#[track_caller]
fn send(&self, msg: loader::Message) {
self.sender.send(msg).unwrap();
}
2020-06-11 09:04:09 +00:00
}
fn read(path: &AbsPath) -> Option<Vec<u8>> {
std::fs::read(path).ok()
}
fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
2021-08-15 12:46:13 +00:00
res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
2020-06-11 09:04:09 +00:00
}
/// Is `path` a symlink to a parent directory?
///
/// Including this path is guaranteed to cause an infinite loop. This
/// heuristic is not sufficient to catch all symlink cycles (it's
/// possible to construct cycle using two or more symlinks), but it
/// catches common cases.
fn path_might_be_cyclic(path: &Path) -> bool {
let Ok(destination) = std::fs::read_link(path) else {
return false;
};
// If the symlink is of the form "../..", it's a parent symlink.
let is_relative_parent =
destination.components().all(|c| matches!(c, Component::CurDir | Component::ParentDir));
is_relative_parent || path.starts_with(destination)
}