mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-16 17:07:14 +03:00
inotify alert (#15027)
Release Notes: - linux: Show an error and troubleshooting steps for inotify limits (#10310)
This commit is contained in:
parent
41a3e78b1e
commit
b0c525af5f
@ -140,10 +140,7 @@ pub struct RealFs {
|
|||||||
git_binary_path: Option<PathBuf>,
|
git_binary_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RealWatcher {
|
pub struct RealWatcher {}
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fs_watcher: parking_lot::Mutex<notify::INotifyWatcher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RealFs {
|
impl RealFs {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@ -472,29 +469,29 @@ impl Fs for RealFs {
|
|||||||
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
|
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
|
||||||
let root_path = path.to_path_buf();
|
let root_path = path.to_path_buf();
|
||||||
|
|
||||||
let file_watcher = notify::recommended_watcher({
|
watcher::global(|g| {
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let pending_paths = pending_paths.clone();
|
let pending_paths = pending_paths.clone();
|
||||||
move |event: Result<notify::Event, _>| {
|
g.add(move |event: ¬ify::Event| {
|
||||||
if let Some(event) = event.log_err() {
|
let mut paths = event
|
||||||
let mut paths = event.paths;
|
.paths
|
||||||
paths.retain(|path| path.starts_with(&root_path));
|
.iter()
|
||||||
if !paths.is_empty() {
|
.filter(|path| path.starts_with(&root_path))
|
||||||
paths.sort();
|
.cloned()
|
||||||
let mut pending_paths = pending_paths.lock();
|
.collect::<Vec<_>>();
|
||||||
if pending_paths.is_empty() {
|
if !paths.is_empty() {
|
||||||
tx.try_send(()).ok();
|
paths.sort();
|
||||||
}
|
let mut pending_paths = pending_paths.lock();
|
||||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
|
if pending_paths.is_empty() {
|
||||||
|
tx.try_send(()).ok();
|
||||||
}
|
}
|
||||||
|
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.expect("Could not start file watcher");
|
.log_err();
|
||||||
|
|
||||||
let watcher = Arc::new(RealWatcher {
|
let watcher = Arc::new(RealWatcher {});
|
||||||
fs_watcher: parking_lot::Mutex::new(file_watcher),
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
|
watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
|
||||||
|
|
||||||
@ -622,18 +619,16 @@ impl Watcher for RealWatcher {
|
|||||||
impl Watcher for RealWatcher {
|
impl Watcher for RealWatcher {
|
||||||
fn add(&self, path: &Path) -> Result<()> {
|
fn add(&self, path: &Path) -> Result<()> {
|
||||||
use notify::Watcher;
|
use notify::Watcher;
|
||||||
|
Ok(watcher::global(|w| {
|
||||||
self.fs_watcher
|
w.inotify
|
||||||
.lock()
|
.lock()
|
||||||
.watch(path, notify::RecursiveMode::NonRecursive)?;
|
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||||
Ok(())
|
})??)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, path: &Path) -> Result<()> {
|
fn remove(&self, path: &Path) -> Result<()> {
|
||||||
use notify::Watcher;
|
use notify::Watcher;
|
||||||
|
Ok(watcher::global(|w| w.inotify.lock().unwatch(path))??)
|
||||||
self.fs_watcher.lock().unwatch(path)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1795,3 +1790,49 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod watcher {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub struct GlobalWatcher {
|
||||||
|
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||||
|
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||||
|
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalWatcher {
|
||||||
|
pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
|
||||||
|
self.watchers.lock().push(Box::new(cb))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
|
||||||
|
OnceLock::new();
|
||||||
|
|
||||||
|
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||||
|
let Some(event) = event.log_err() else { return };
|
||||||
|
global::<()>(move |watcher| {
|
||||||
|
for f in watcher.watchers.lock().iter() {
|
||||||
|
f(&event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||||
|
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||||
|
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||||
|
inotify: Mutex::new(file_watcher),
|
||||||
|
watchers: Default::default(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
match result {
|
||||||
|
Ok(g) => Ok(f(g)),
|
||||||
|
Err(e) => Err(anyhow::anyhow!("{}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -841,6 +841,10 @@ async fn test_write_file(cx: &mut TestAppContext) {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fs::watcher::global(|_| {}).unwrap();
|
||||||
|
|
||||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||||
.await;
|
.await;
|
||||||
tree.flush_fs_events(cx).await;
|
tree.flush_fs_events(cx).await;
|
||||||
|
@ -140,6 +140,25 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if let Err(e) = fs::watcher::global(|_| {}) {
|
||||||
|
let message = format!(db::indoc!{r#"
|
||||||
|
inotify_init returned {}
|
||||||
|
|
||||||
|
This may be due to system-wide limits on inotify instances. For troubleshooting see: https://zed.dev/docs/linux
|
||||||
|
"#}, e);
|
||||||
|
let prompt = cx.prompt(PromptLevel::Critical, "Could not start inotify", Some(&message),
|
||||||
|
&["Troubleshoot and Quit"]);
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
if prompt.await == Ok(0) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.open_url("https://zed.dev/docs/linux#could-not-start-inotify");
|
||||||
|
cx.quit();
|
||||||
|
}).ok();
|
||||||
|
}
|
||||||
|
}).detach()
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(specs) = cx.gpu_specs() {
|
if let Some(specs) = cx.gpu_specs() {
|
||||||
log::info!("Using GPU: {:?}", specs);
|
log::info!("Using GPU: {:?}", specs);
|
||||||
if specs.is_software_emulated && std::env::var("ZED_ALLOW_EMULATED_GPU").is_err() {
|
if specs.is_software_emulated && std::env::var("ZED_ALLOW_EMULATED_GPU").is_err() {
|
||||||
|
@ -122,3 +122,13 @@ All of these features are provided by XDG desktop portals, specifically:
|
|||||||
- `org.freedesktop.portal.Secret`, or `org.freedesktop.Secrets`
|
- `org.freedesktop.portal.Secret`, or `org.freedesktop.Secrets`
|
||||||
|
|
||||||
Some window managers, such as `Hyprland`, don't provide a file picker by default. See [this list](https://wiki.archlinux.org/title/XDG_Desktop_Portal#List_of_backends_and_interfaces) as a starting point for alternatives. `KDE` doesn't implement the secret portal, installing `gnome-keyring` may solve this.
|
Some window managers, such as `Hyprland`, don't provide a file picker by default. See [this list](https://wiki.archlinux.org/title/XDG_Desktop_Portal#List_of_backends_and_interfaces) as a starting point for alternatives. `KDE` doesn't implement the secret portal, installing `gnome-keyring` may solve this.
|
||||||
|
|
||||||
|
### Could not start inotify
|
||||||
|
|
||||||
|
Zed relies on inotify to watch your filesystem for changes. If you cannot start inotify then Zed will not work reliably.
|
||||||
|
|
||||||
|
If you are seeing "too many open files" then first try `sysctl fs.inotify`.
|
||||||
|
* You should see that max_user_instances is 128 or higher (you can change the limit with `sudo sysctl fs.inotify.max_user_instances=1024`). Zed needs only 1 inotify instance.
|
||||||
|
* You should see that `max_user_watches` is 8000 or higher (you can change the limit with `sudo sysctl fs.inotify.max_user_watches=64000`). Zed needs one watch per directory in all your open projects + one per git repository + a handful more for settings, themes, keymaps, extensions.
|
||||||
|
|
||||||
|
It is also possible that you are running out of file descriptors. You can check the limits with `ulimit` and update them by editing `/etc/security/limits.conf`.
|
||||||
|
Loading…
Reference in New Issue
Block a user