fs: Polling backend support (#54481)

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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #51340
Release Notes:

- Linux/WSL: Added polling filesystem watching support in order to
support file watching on file systems that do emit events through
`inotify`, for example `/mnt/c` in WSL, network filesystems, and FUSE
mounts. Polling should be automatically chosen over `inotify` when
necessary, however it can be manually chosen by setting the
`ZED_FILE_WATCHER_MODE=poll` env var

---------

Co-authored-by: Lilith River <lilith@imazen.io>
Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
This commit is contained in:
authored and GitHub committed 2026-04-23 11:40:49 +00:00
1 parent a17a2883bd
commit 68f1d088c1
4 files changed
+459 -173

No files matched your search

Generated
+1
View File
@@ -6606,6 +6606,7 @@ dependencies = [
"serde",
"serde_json",
"smol",
"telemetry",
"tempfile",
"text",
"thiserror 2.0.17",
+1
View File
@@ -36,6 +36,7 @@ thiserror.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
telemetry.workspace = true
tempfile.workspace = true
text.workspace = true
time.workspace = true
+150 -8
View File
@@ -72,6 +72,133 @@ pub trait Watcher: Send + Sync {
fn remove(&self, path: &Path) -> Result<()>;
}
/// Detect whether a path requires polling instead of native file watching.
///
/// Returns `true` for filesystem types where inotify/FSEvents/ReadDirectoryChanges
/// silently fail to deliver events: 9P (WSL drvfs), NFS, CIFS/SMB, FUSE (sshfs), etc.
///
/// Can be overridden with the `ZED_FILE_WATCHER_MODE` environment variable:
/// - `native` — always use native OS watcher
/// - `poll` — always use polling
/// - `auto` (default) — auto-detect based on filesystem type
pub fn requires_poll_watcher(path: &Path) -> bool {
match std::env::var("ZED_FILE_WATCHER_MODE")
.as_deref()
.unwrap_or("auto")
{
"native" => return false,
"poll" => return true,
_ => {}
}
#[cfg(target_os = "linux")]
{
let path = effective_watch_path(path);
return detect_requires_poll_watcher_linux(&path);
}
#[cfg(not(target_os = "linux"))]
{
let _ = path;
false
}
}
pub fn effective_watch_path(path: &Path) -> PathBuf {
if path.exists() {
return path.to_path_buf();
}
for ancestor in path.ancestors() {
if ancestor.exists() {
return ancestor.to_path_buf();
}
}
path.to_path_buf()
}
#[cfg(target_os = "linux")]
fn detect_requires_poll_watcher_linux(path: &Path) -> bool {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
// Check filesystem type via statfs
let c_path = match CString::new(path.as_os_str().as_bytes()) {
Ok(p) => p,
Err(_) => return false,
};
let mut stat: libc::statfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statfs(c_path.as_ptr(), &mut stat) } != 0 {
return false;
}
// Filesystem magic numbers where inotify does not deliver events.
// These are defined in linux/magic.h and statfs(2).
const V9FS_MAGIC: i64 = 0x01021997; // Plan 9 / WSL2 interop (drvfs)
const NFS_SUPER_MAGIC: i64 = 0x6969;
const CIFS_MAGIC: i64 = 0xFF534D42u32 as i64; // CIFS/SMB
const SMB_SUPER_MAGIC: i64 = 0x517B;
const SMB2_MAGIC: i64 = 0xFE534D42u32 as i64;
const FUSE_SUPER_MAGIC: i64 = 0x65735546; // FUSE (includes sshfs)
let fs_type = stat.f_type;
if fs_type == V9FS_MAGIC
|| fs_type == NFS_SUPER_MAGIC
|| fs_type == CIFS_MAGIC
|| fs_type == SMB_SUPER_MAGIC
|| fs_type == SMB2_MAGIC
|| fs_type == FUSE_SUPER_MAGIC
{
log::info!(
"Detected network/virtual filesystem (type 0x{:x}) at {}, using poll watcher",
fs_type,
path.display()
);
return true;
}
// Also check for WSL + /mnt/<drive>/ pattern as a fallback
// in case statfs returns an unexpected type for drvfs
if is_wsl_drvfs_path(path) {
log::info!(
"Detected WSL drvfs mount at {}, using poll watcher",
path.display()
);
return true;
}
false
}
#[cfg(target_os = "linux")]
fn is_wsl_drvfs_path(path: &Path) -> bool {
// Only relevant inside WSL
if std::env::var_os("WSL_DISTRO_NAME").is_none() {
if let Ok(version) = std::fs::read_to_string("/proc/version") {
let v = version.to_lowercase();
if !v.contains("microsoft") && !v.contains("wsl") {
return false;
}
} else {
return false;
}
}
// Windows drives are mounted at /mnt/c, /mnt/d, etc.
let path_str = match path.to_str() {
Some(s) => s,
None => return false,
};
if !path_str.starts_with("/mnt/") || path_str.len() < 6 {
return false;
}
let after_mnt = &path_str[5..];
after_mnt.starts_with(|c: char| c.is_ascii_alphabetic())
&& (after_mnt.len() == 1 || after_mnt.as_bytes()[1] == b'/')
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum PathEventKind {
Removed,
@@ -1070,19 +1197,34 @@ impl Fs for RealFs {
use util::{ResultExt as _, paths::SanitizedPath};
let executor = self.executor.clone();
let use_poll = requires_poll_watcher(path);
let watch_path = effective_watch_path(path);
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
if let Err(e) = watcher.add(path)
&& let Some(parent) = path.parent()
&& let Err(parent_e) = watcher.add(parent)
{
let mode = if use_poll {
log::info!(
"Using poll watcher ({}ms interval) for {}",
fs_watcher::poll_interval().as_millis(),
path.display()
);
telemetry::event!("fs_watcher_poll", path = path.display().to_string());
fs_watcher::WatcherMode::Poll
} else {
fs_watcher::WatcherMode::Native
};
let watcher: Arc<dyn Watcher> = Arc::new(fs_watcher::FsWatcher::new(
tx.clone(),
pending_paths.clone(),
mode,
));
if let Err(e) = watcher.add(&watch_path) {
log::warn!(
"Failed to watch {} and its parent directory {}:\n{e}\n{parent_e}",
"Failed to watch {} using {}:\n{e}",
path.display(),
parent.display()
watch_path.display()
);
}
+307 -165
View File
@@ -4,27 +4,38 @@ use std::{
collections::{BTreeMap, HashMap},
ops::DerefMut,
path::Path,
sync::{Arc, OnceLock},
sync::{Arc, LazyLock, OnceLock},
time::Duration,
};
use util::{ResultExt, paths::SanitizedPath};
use crate::{PathEvent, PathEventKind, Watcher};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum WatcherMode {
#[default]
Native,
Poll,
}
pub struct FsWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
registrations: Mutex<BTreeMap<Arc<std::path::Path>, WatcherRegistrationId>>,
mode: WatcherMode,
}
impl FsWatcher {
pub fn new(
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
mode: WatcherMode,
) -> Self {
Self {
tx,
pending_path_events,
registrations: Default::default(),
mode,
}
}
}
@@ -37,11 +48,10 @@ impl Drop for FsWatcher {
std::mem::swap(old.deref_mut(), &mut registrations);
}
let _ = global(|g| {
for (_, registration) in registrations {
g.remove(registration);
}
});
let global_watcher = global_watcher();
for (_, registration) in registrations {
global_watcher.remove(registration);
}
}
}
@@ -49,13 +59,10 @@ impl Watcher for FsWatcher {
fn add(&self, path: &std::path::Path) -> anyhow::Result<()> {
log::trace!("watcher add: {path:?}");
let tx = self.tx.clone();
let pending_paths = self.pending_path_events.clone();
let pending_path_events = self.pending_path_events.clone();
#[cfg(any(target_os = "windows", target_os = "macos"))]
{
// Return early if an ancestor of this path was already being watched.
// saves a huge amount of memory
if let Some((watched_path, _)) = self
if (self.mode == WatcherMode::Poll || cfg!(any(target_os = "windows", target_os = "macos")))
&& let Some((watched_path, _)) = self
.registrations
.lock()
.range::<std::path::Path, _>((
@@ -63,86 +70,36 @@ impl Watcher for FsWatcher {
std::ops::Bound::Included(path),
))
.next_back()
&& path.starts_with(watched_path.as_ref())
{
log::trace!(
"path to watch is covered by existing registration: {path:?}, {watched_path:?}"
);
return Ok(());
}
}
#[cfg(target_os = "linux")]
&& path.starts_with(watched_path.as_ref())
{
if self.registrations.lock().contains_key(path) {
log::trace!("path to watch is already watched: {path:?}");
return Ok(());
}
log::trace!(
"path to watch is covered by existing registration: {path:?}, {watched_path:?}"
);
return Ok(());
}
if self.registrations.lock().contains_key(path) {
log::trace!("path to watch is already watched: {path:?}");
return Ok(());
}
let root_path = SanitizedPath::new_arc(path);
let path: Arc<std::path::Path> = path.into();
#[cfg(any(target_os = "windows", target_os = "macos"))]
let mode = notify::RecursiveMode::Recursive;
#[cfg(target_os = "linux")]
let mode = notify::RecursiveMode::NonRecursive;
let registration_path = path.clone();
let registration_id = global({
let watch_path = path.clone();
let callback_path = path;
|g| {
g.add(watch_path, mode, move |event: &notify::Event| {
let registration_id = global_watcher().add(
path.clone(),
self.mode,
move |result: Result<&notify::Event, &notify::Error>| match result {
Ok(event) => {
log::trace!("watcher received event: {event:?}");
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut path_events = event
.paths
.iter()
.filter_map(|event_path| {
let event_path = SanitizedPath::new(event_path);
event_path.starts_with(&root_path).then(|| PathEvent {
path: event_path.as_path().to_path_buf(),
kind,
})
})
.collect::<Vec<_>>();
let is_rescan_event = event.need_rescan();
if is_rescan_event {
log::warn!(
"filesystem watcher lost sync for {callback_path:?}; scheduling rescan"
);
// we only keep the first event per path below, this ensures it will be the rescan event
// we'll remove any existing pending events for the same reason once we have the lock below
path_events.retain(|p| &p.path != callback_path.as_ref());
path_events.push(PathEvent {
path: callback_path.to_path_buf(),
kind: Some(PathEventKind::Rescan),
});
}
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
coalesce_pending_rescans(&mut pending_paths, &mut path_events);
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
})
}
})??;
push_notify_event(&tx, &pending_path_events, &root_path, path.as_ref(), event);
}
Err(error) => {
push_notify_error(&tx, &pending_path_events, path.as_ref(), error);
}
},
)?;
self.registrations
.lock()
@@ -157,10 +114,85 @@ impl Watcher for FsWatcher {
return Ok(());
};
global(|w| w.remove(registration))
global_watcher().remove(registration);
Ok(())
}
}
fn enqueue_path_events(
tx: &smol::channel::Sender<()>,
pending_path_events: &Arc<Mutex<Vec<PathEvent>>>,
mut path_events: Vec<PathEvent>,
) {
if path_events.is_empty() {
return;
}
path_events.sort();
let mut pending_paths = pending_path_events.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
coalesce_pending_rescans(&mut pending_paths, &mut path_events);
util::extend_sorted(&mut *pending_paths, path_events, usize::MAX, |a, b| {
a.path.cmp(&b.path)
});
}
fn push_notify_event(
tx: &smol::channel::Sender<()>,
pending_path_events: &Arc<Mutex<Vec<PathEvent>>>,
root_path: &SanitizedPath,
watched_root: &Path,
event: &notify::Event,
) {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut path_events = event
.paths
.iter()
.filter_map(|event_path| {
let event_path = SanitizedPath::new(event_path);
event_path.starts_with(root_path).then(|| PathEvent {
path: event_path.as_path().to_path_buf(),
kind,
})
})
.collect::<Vec<_>>();
if event.need_rescan() {
log::warn!("filesystem watcher lost sync for {watched_root:?}; scheduling rescan");
path_events.retain(|path_event| path_event.path != watched_root);
path_events.push(PathEvent {
path: watched_root.to_path_buf(),
kind: Some(PathEventKind::Rescan),
});
}
enqueue_path_events(tx, pending_path_events, path_events);
}
fn push_notify_error(
tx: &smol::channel::Sender<()>,
pending_path_events: &Arc<Mutex<Vec<PathEvent>>>,
watched_root: &Path,
error: &notify::Error,
) {
log::warn!("watcher error for {watched_root:?}: {error}");
enqueue_path_events(
tx,
pending_path_events,
vec![PathEvent {
path: watched_root.to_path_buf(),
kind: Some(PathEventKind::Rescan),
}],
);
}
fn coalesce_pending_rescans(pending_paths: &mut Vec<PathEvent>, path_events: &mut Vec<PathEvent>) {
if !path_events
.iter()
@@ -215,29 +247,34 @@ fn is_covered_rescan(kind: Option<PathEventKind>, path: &Path, ancestor: &Path)
pub struct WatcherRegistrationId(u32);
struct WatcherRegistrationState {
callback: Arc<dyn Fn(&notify::Event) + Send + Sync>,
callback: Arc<dyn for<'a> Fn(Result<&'a notify::Event, &'a notify::Error>) + Send + Sync>,
path: Arc<std::path::Path>,
mode: WatcherMode,
}
struct WatcherState {
watchers: HashMap<WatcherRegistrationId, WatcherRegistrationState>,
path_registrations: HashMap<Arc<std::path::Path>, u32>,
native_path_registrations: HashMap<Arc<std::path::Path>, u32>,
poll_path_registrations: HashMap<Arc<std::path::Path>, u32>,
last_registration: WatcherRegistrationId,
}
impl WatcherState {
fn path_registrations(&mut self, mode: WatcherMode) -> &mut HashMap<Arc<std::path::Path>, u32> {
match mode {
WatcherMode::Native => &mut self.native_path_registrations,
WatcherMode::Poll => &mut self.poll_path_registrations,
}
}
}
pub struct GlobalWatcher {
state: Mutex<WatcherState>,
// DANGER: never keep the state lock while holding the watcher lock
// two mutexes because calling watcher.add triggers an watcher.event, which needs watchers.
#[cfg(target_os = "linux")]
watcher: Mutex<notify::INotifyWatcher>,
#[cfg(target_os = "freebsd")]
watcher: Mutex<notify::KqueueWatcher>,
#[cfg(target_os = "windows")]
watcher: Mutex<notify::ReadDirectoryChangesWatcher>,
#[cfg(target_os = "macos")]
watcher: Mutex<notify::FsEventWatcher>,
// DANGER: never keep state lock while holding watcher lock
// two mutexes because calling watcher.add triggers watcher.event, which needs watchers.
native_watcher: Mutex<Option<notify::RecommendedWatcher>>,
poll_watcher: Mutex<Option<notify::PollWatcher>>,
}
impl GlobalWatcher {
@@ -245,27 +282,17 @@ impl GlobalWatcher {
fn add(
&self,
path: Arc<std::path::Path>,
mode: notify::RecursiveMode,
cb: impl Fn(&notify::Event) + Send + Sync + 'static,
mode: WatcherMode,
cb: impl for<'a> Fn(Result<&'a notify::Event, &'a notify::Error>) + Send + Sync + 'static,
) -> anyhow::Result<WatcherRegistrationId> {
use notify::Watcher;
let mut state = self.state.lock();
let registrations_for_mode = state.path_registrations(mode);
let path_already_covered =
path_already_covered(path.as_ref(), registrations_for_mode, mode);
// Check if this path is already covered by an existing watched ancestor path.
// On macOS and Windows, watching is recursive, so we don't need to watch
// child paths if an ancestor is already being watched.
#[cfg(any(target_os = "windows", target_os = "macos"))]
let path_already_covered = state.path_registrations.keys().any(|existing| {
path.starts_with(existing.as_ref()) && path.as_ref() != existing.as_ref()
});
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
let path_already_covered = false;
if !path_already_covered && !state.path_registrations.contains_key(&path) {
if !path_already_covered && !registrations_for_mode.contains_key(&path) {
drop(state);
self.watcher.lock().watch(&path, mode)?;
self.watch(&path, mode)?;
state = self.state.lock();
}
@@ -275,38 +302,190 @@ impl GlobalWatcher {
let registration_state = WatcherRegistrationState {
callback: Arc::new(cb),
path: path.clone(),
mode,
};
state.watchers.insert(id, registration_state);
*state.path_registrations.entry(path).or_insert(0) += 1;
*state.path_registrations(mode).entry(path).or_insert(0) += 1;
Ok(id)
}
pub fn remove(&self, id: WatcherRegistrationId) {
use notify::Watcher;
let mut state = self.state.lock();
let Some(registration_state) = state.watchers.remove(&id) else {
return;
};
let Some(count) = state.path_registrations.get_mut(&registration_state.path) else {
let path_registrations = state.path_registrations(registration_state.mode);
let Some(count) = path_registrations.get_mut(&registration_state.path) else {
return;
};
*count -= 1;
if *count == 0 {
state.path_registrations.remove(&registration_state.path);
path_registrations.remove(&registration_state.path);
let path_still_covered = path_already_covered(
registration_state.path.as_ref(),
path_registrations,
registration_state.mode,
);
drop(state);
self.watcher
.lock()
.unwatch(&registration_state.path)
.log_err();
if !path_still_covered {
drop(state);
self.unwatch(&registration_state.path, registration_state.mode)
.log_err();
}
}
}
fn watch(&self, path: &Path, mode: WatcherMode) -> anyhow::Result<()> {
use notify::Watcher;
match mode {
WatcherMode::Native => {
self.ensure_native_watcher()?;
self.native_watcher
.lock()
.as_mut()
.expect("native watcher initialized")
.watch(
path,
if cfg!(any(target_os = "windows", target_os = "macos")) {
notify::RecursiveMode::Recursive
} else {
notify::RecursiveMode::NonRecursive
},
)?;
}
WatcherMode::Poll => {
self.ensure_poll_watcher()?;
self.poll_watcher
.lock()
.as_mut()
.expect("poll watcher initialized")
.watch(path, notify::RecursiveMode::Recursive)?;
}
}
Ok(())
}
fn unwatch(&self, path: &Path, mode: WatcherMode) -> anyhow::Result<()> {
use notify::Watcher;
match mode {
WatcherMode::Native => {
if let Some(watcher) = self.native_watcher.lock().as_mut() {
watcher.unwatch(path)?;
}
}
WatcherMode::Poll => {
if let Some(watcher) = self.poll_watcher.lock().as_mut() {
watcher.unwatch(path)?;
}
}
}
Ok(())
}
fn ensure_native_watcher(&self) -> anyhow::Result<()> {
if self.native_watcher.lock().is_some() {
return Ok(());
}
let watcher = notify::recommended_watcher(handle_native_event)?;
*self.native_watcher.lock() = Some(watcher);
Ok(())
}
fn ensure_poll_watcher(&self) -> anyhow::Result<()> {
if self.poll_watcher.lock().is_some() {
return Ok(());
}
let config = notify::Config::default().with_poll_interval(*POLL_INTERVAL);
let watcher = notify::PollWatcher::new(handle_poll_event, config)?;
*self.poll_watcher.lock() = Some(watcher);
Ok(())
}
}
static FS_WATCHER_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
OnceLock::new();
fn path_already_covered(
path: &Path,
path_registrations: &HashMap<Arc<std::path::Path>, u32>,
mode: WatcherMode,
) -> bool {
(mode == WatcherMode::Poll || cfg!(any(target_os = "windows", target_os = "macos")))
&& path_registrations
.keys()
.any(|existing| path.starts_with(existing.as_ref()) && path != existing.as_ref())
}
static POLL_INTERVAL: LazyLock<Duration> = LazyLock::new(|| {
let poll_ms: u64 = std::env::var("ZED_FILE_WATCHER_POLL_MS")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(2000)
.clamp(500, 30000);
Duration::from_millis(poll_ms)
});
pub fn poll_interval() -> Duration {
*POLL_INTERVAL
}
static FS_WATCHER_INSTANCE: OnceLock<GlobalWatcher> = OnceLock::new();
fn global_watcher() -> &'static GlobalWatcher {
FS_WATCHER_INSTANCE.get_or_init(|| GlobalWatcher {
state: Mutex::new(WatcherState {
watchers: Default::default(),
native_path_registrations: Default::default(),
poll_path_registrations: Default::default(),
last_registration: Default::default(),
}),
native_watcher: Mutex::new(None),
poll_watcher: Mutex::new(None),
})
}
fn handle_native_event(event: Result<notify::Event, notify::Error>) {
handle_event(WatcherMode::Native, event);
}
fn handle_poll_event(event: Result<notify::Event, notify::Error>) {
handle_event(WatcherMode::Poll, event);
}
fn handle_event(mode: WatcherMode, event: Result<notify::Event, notify::Error>) {
log::trace!("global handle event for {mode:?}: {event:?}");
let callbacks = {
let state = global_watcher().state.lock();
state
.watchers
.values()
.filter(|registration| registration.mode == mode)
.map(|registration| registration.callback.clone())
.collect::<Vec<_>>()
};
match event {
Ok(event) => {
if matches!(event.kind, EventKind::Access(_)) {
return;
}
for callback in callbacks {
callback(Ok(&event));
}
}
Err(error) => {
for callback in callbacks {
callback(Err(&error));
}
}
}
}
#[cfg(test)]
mod tests {
@@ -399,45 +578,8 @@ mod tests {
}
}
fn handle_event(event: Result<notify::Event, notify::Error>) {
log::trace!("global handle event: {event:?}");
// Filter out access events, which could lead to a weird bug on Linux after upgrading notify
// https://github.com/zed-industries/zed/actions/runs/14085230504/job/39449448832
let Some(event) = event
.log_err()
.filter(|event| !matches!(event.kind, EventKind::Access(_)))
else {
return;
};
global::<()>(move |watcher| {
let callbacks = {
let state = watcher.state.lock();
state
.watchers
.values()
.map(|r| r.callback.clone())
.collect::<Vec<_>>()
};
for callback in callbacks {
callback(&event);
}
})
.log_err();
}
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
let result = FS_WATCHER_INSTANCE.get_or_init(|| {
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
state: Mutex::new(WatcherState {
watchers: Default::default(),
path_registrations: Default::default(),
last_registration: Default::default(),
}),
watcher: Mutex::new(file_watcher),
})
});
match result {
Ok(g) => Ok(f(g)),
Err(e) => Err(anyhow::anyhow!("{e}")),
}
let global_watcher = global_watcher();
global_watcher.ensure_native_watcher()?;
Ok(f(global_watcher))
}