performance(plugins): use a debounced fs watcher (#2546)

* performance(plugins): use a debounced fs watcher

* style(fmt): rustfmt

* fix(macos): use recommended watcher
This commit is contained in:
Aram Drevekenin 2023-06-16 21:04:22 +02:00 committed by GitHub
parent 317bdfc902
commit b7436742cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 56 deletions

24
Cargo.lock generated
View File

@ -1005,6 +1005,15 @@ dependencies = [
"instant",
]
[[package]]
name = "file-id"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13be71e6ca82e91bc0cb862bebaac0b2d1924a5a1d970c822b2f98b63fda8c3"
dependencies = [
"winapi-util",
]
[[package]]
name = "filedescriptor"
version = "0.8.2"
@ -1831,6 +1840,19 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "notify-debouncer-full"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4812c1eb49be776fb8df4961623bdc01ec9dfdc1abe8211ceb09150a2e64219"
dependencies = [
"crossbeam-channel",
"file-id",
"notify",
"parking_lot 0.12.1",
"walkdir",
]
[[package]]
name = "ntapi"
version = "0.3.7"
@ -4424,7 +4446,7 @@ dependencies = [
"log4rs",
"miette",
"nix 0.23.1",
"notify",
"notify-debouncer-full",
"once_cell",
"percent-encoding",
"regex",

View File

@ -13,7 +13,7 @@ use std::{
};
use wasmer::{Instance, Module, Store, Value};
use zellij_utils::async_std::task::{self, JoinHandle};
use zellij_utils::notify::{RecommendedWatcher, Watcher};
use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap};
use crate::{
background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders,
@ -50,7 +50,7 @@ pub struct WasmBridge {
loading_plugins: HashMap<(PluginId, RunPlugin), JoinHandle<()>>, // plugin_id to join-handle
pending_plugin_reloads: HashSet<RunPlugin>,
path_to_default_shell: PathBuf,
watcher: Option<RecommendedWatcher>,
watcher: Option<Debouncer<RecommendedWatcher, FileIdMap>>,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
@ -530,7 +530,9 @@ impl WasmBridge {
for plugin_id in &plugin_ids {
drop(self.unload_plugin(*plugin_id));
}
drop(self.watcher.as_mut().map(|w| w.unwatch(&self.zellij_cwd)));
if let Some(watcher) = self.watcher.take() {
watcher.stop_nonblocking();
}
}
fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<&RunPlugin> {
self.loading_plugins

View File

@ -3,61 +3,108 @@ use std::path::PathBuf;
use crate::thread_bus::ThreadSenders;
use std::path::Path;
use std::time::Duration;
use zellij_utils::{data::Event, errors::prelude::*};
use zellij_utils::notify_debouncer_full::{
new_debouncer,
notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher},
DebounceEventResult, Debouncer, FileIdMap,
};
use zellij_utils::{data::Event, errors::prelude::Result};
use zellij_utils::notify::{self, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
pub fn watch_filesystem(senders: ThreadSenders, zellij_cwd: &Path) -> Result<RecommendedWatcher> {
const DEBOUNCE_DURATION_MS: u64 = 500;
pub fn watch_filesystem(
senders: ThreadSenders,
zellij_cwd: &Path,
) -> Result<Debouncer<RecommendedWatcher, FileIdMap>> {
let path_prefix_in_plugins = PathBuf::from("/host");
let current_dir = PathBuf::from(zellij_cwd);
let mut watcher = notify::recommended_watcher({
move |res: notify::Result<notify::Event>| match res {
Ok(event) => {
let paths: Vec<PathBuf> = event
.paths
.iter()
.map(|p| {
let stripped_prefix_path =
p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
path_prefix_in_plugins.join(stripped_prefix_path)
let mut debouncer = new_debouncer(
Duration::from_millis(DEBOUNCE_DURATION_MS),
None,
move |result: DebounceEventResult| match result {
Ok(events) => {
let mut create_events = vec![];
let mut read_events = vec![];
let mut update_events = vec![];
let mut delete_events = vec![];
for event in events {
match event.kind {
EventKind::Access(_) => read_events.push(event),
EventKind::Create(_) => create_events.push(event),
EventKind::Modify(_) => update_events.push(event),
EventKind::Remove(_) => delete_events.push(event),
_ => {},
}
}
let create_paths: Vec<PathBuf> = create_events
.drain(..)
.map(|e| {
e.paths
.iter()
.map(|p| {
let stripped_prefix_path =
p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
path_prefix_in_plugins.join(stripped_prefix_path)
})
.collect()
})
.collect();
match event.kind {
EventKind::Access(_) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
None,
None,
Event::FileSystemRead(paths),
)]));
},
EventKind::Create(_) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
None,
None,
Event::FileSystemCreate(paths),
)]));
},
EventKind::Modify(_) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
None,
None,
Event::FileSystemUpdate(paths),
)]));
},
EventKind::Remove(_) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
None,
None,
Event::FileSystemDelete(paths),
)]));
},
_ => {},
}
let read_paths: Vec<PathBuf> = read_events
.drain(..)
.map(|e| {
e.paths
.iter()
.map(|p| {
let stripped_prefix_path =
p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
path_prefix_in_plugins.join(stripped_prefix_path)
})
.collect()
})
.collect();
let update_paths: Vec<PathBuf> = update_events
.drain(..)
.map(|e| {
e.paths
.iter()
.map(|p| {
let stripped_prefix_path =
p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
path_prefix_in_plugins.join(stripped_prefix_path)
})
.collect()
})
.collect();
let delete_paths: Vec<PathBuf> = delete_events
.drain(..)
.map(|e| {
e.paths
.iter()
.map(|p| {
let stripped_prefix_path =
p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
path_prefix_in_plugins.join(stripped_prefix_path)
})
.collect()
})
.collect();
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![
(None, None, Event::FileSystemRead(read_paths)),
(None, None, Event::FileSystemCreate(create_paths)),
(None, None, Event::FileSystemUpdate(update_paths)),
(None, None, Event::FileSystemDelete(delete_paths)),
]));
},
Err(e) => log::error!("watch error: {:?}", e),
}
})?;
Err(errors) => errors
.iter()
.for_each(|error| log::error!("watch error: {error:?}")),
},
)?;
watcher.watch(zellij_cwd, RecursiveMode::Recursive)?;
Ok(watcher)
debouncer
.watcher()
.watch(zellij_cwd, RecursiveMode::Recursive)?;
Ok(debouncer)
}

View File

@ -40,7 +40,6 @@ tempfile = "3.2.0"
kdl = { version = "4.5.0", features = ["span"] }
shellexpand = "3.0.0"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
notify = "6.0.0"
async-channel = "1.8.0"
#[cfg(not(target_family = "wasm"))]
@ -50,6 +49,7 @@ log4rs = "1.2.0"
signal-hook = "0.3"
interprocess = "1.2.1"
async-std = { version = "1.3.0", features = ["unstable"] }
notify-debouncer-full = "0.1.0"
[dev-dependencies]
insta = { version = "1.6.0", features = ["backtrace"] }

View File

@ -20,6 +20,6 @@ pub mod logging; // Requires log4rs
#[cfg(not(target_family = "wasm"))]
pub use ::{
anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix, notify,
regex, serde, signal_hook, tempfile, termwiz, vte,
anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix,
notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte,
};