Unnamed repository; edit this file 'description' to name the repository.
fix: Support filesystems that don't send Create events
On some filesystems, particularly FUSE on Linux, we don't get Create(...) events. We do get Access(Open(Any)) events, so handle those consistently with create/modify/remove events. This fixes missed file notifications when using Sapling SCM with EdenFS, although I believe the problem can occur on other FUSE environments. Reproduction: Commit a change with Sapling that adds a new file foo.rs and references it with `mod foo;` in lib.rs. Configure rust-analyzer as follows: ``` { "rust-analyzer.files.watcher": "server", "rust-analyzer.server.extraEnv": { "RA_LOG": "vfs_notify=debug" }, } ``` Go to the previous commit, restart rust-analyzer, then go to the next commit. The logs only show: ``` 2026-03-18T07:16:54.211788903-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/foo.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.211906733-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/foo.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.216467168-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/lib.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.216811304-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/lib.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) ``` Observe that `mod foo;` has a red squiggle and shows "unresolved module, can't find module file: foo.rs, or foo/mod.rs". This commit fixes that.
Wilfred Hughes 8 weeks ago
parent 38052f1 · commit 19e0346
-rw-r--r--crates/vfs-notify/src/lib.rs29
1 files changed, 26 insertions, 3 deletions
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index f91d830ca0..6465a85d2d 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -14,7 +14,7 @@ use std::{
};
use crossbeam_channel::{Receiver, Sender, select, unbounded};
-use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
+use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher, event::AccessKind};
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rayon::iter::{IndexedParallelIterator as _, IntoParallelIterator as _, ParallelIterator};
use rustc_hash::FxHashSet;
@@ -63,6 +63,7 @@ struct NotifyActor {
sender: loader::Sender,
watched_file_entries: FxHashSet<AbsPathBuf>,
watched_dir_entries: Vec<loader::Directories>,
+ seen_paths: FxHashSet<AbsPathBuf>,
// Drop order is significant.
watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
}
@@ -79,6 +80,7 @@ impl NotifyActor {
sender,
watched_dir_entries: Vec::new(),
watched_file_entries: FxHashSet::default(),
+ seen_paths: FxHashSet::default(),
watcher: None,
}
}
@@ -120,6 +122,7 @@ impl NotifyActor {
let n_total = config.load.len();
self.watched_dir_entries.clear();
self.watched_file_entries.clear();
+ self.seen_paths.clear();
self.send(loader::Message::Progress {
n_total,
@@ -195,8 +198,10 @@ impl NotifyActor {
},
Event::NotifyEvent(event) => {
if let Some(event) = log_notify_error(event)
- && let EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) =
- event.kind
+ && let EventKind::Create(_)
+ | EventKind::Modify(_)
+ | EventKind::Remove(_)
+ | EventKind::Access(AccessKind::Open(_)) = event.kind
{
let abs_paths: Vec<AbsPathBuf> = event
.paths
@@ -209,6 +214,24 @@ impl NotifyActor {
})
.collect();
+ let mut saw_new_file = false;
+ for abs_path in &abs_paths {
+ if self.seen_paths.insert(abs_path.clone()) {
+ saw_new_file = true;
+ }
+ }
+
+ // Only consider access events for files that we haven't seen
+ // before.
+ //
+ // This is important on FUSE filesystems, where we may not get a
+ // Create event. In other cases we're about to access the file, so
+ // we don't want an infinite loop where processing an Access event
+ // creates another Access event.
+ if matches!(event.kind, EventKind::Access(_)) && !saw_new_file {
+ continue;
+ }
+
let files = abs_paths
.into_iter()
.filter_map(|path| -> Option<(AbsPathBuf, Option<Vec<u8>>)> {