From 799d8f75bb4bcbf06f794dd23971633486f90906 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 2 Feb 2023 13:13:23 +0300 Subject: [PATCH] Fix notify doing active polling The `notify-debouncer-mini` spawn a thread which checks the events every timeout, which is not desired since we want to avoid active polling. This commit re-implements debouncer based on the `RecommendedWatcher` without adding an extra thread on top and not doing any busy-waiting. Fixes #6652. --- Cargo.lock | 32 ++++++++------ alacritty/Cargo.toml | 2 +- alacritty/src/config/monitor.rs | 74 ++++++++++++++++++++++++--------- 3 files changed, 75 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3b660b7..1d13cbb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ dependencies = [ "glutin", "libc", "log", - "notify-debouncer-mini", + "notify", "objc", "once_cell", "parking_lot 0.12.1", @@ -1149,9 +1149,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" dependencies = [ "bitflags", "crossbeam-channel", @@ -1162,16 +1162,7 @@ dependencies = [ "libc", "mio 0.8.4", "walkdir", - "winapi 0.3.9", -] - -[[package]] -name = "notify-debouncer-mini" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e23e9fa24f094b143c1eb61f90ac6457de87be6987bc70746e0179f7dbc9007b" -dependencies = [ - "notify", + "windows-sys 0.42.0", ] [[package]] @@ -2056,6 +2047,21 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 63edd5c0..0c1fac66 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -31,7 +31,7 @@ serde_yaml = "0.8" serde_json = "1" glutin = { version = "0.30.3", default-features = false, features = ["egl", "wgl"] } winit = { version = "0.28.0", default-features = false, features = ["serde"] } -notify-debouncer-mini = { version = "0.2.1", default-features = false } +notify = "5.1.0" parking_lot = "0.12.0" crossfont = { version = "0.5.0", features = ["force_system_fontconfig"] } copypasta = { version = "0.8.1", default-features = false } diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs index 622a993b..3548fc02 100644 --- a/alacritty/src/config/monitor.rs +++ b/alacritty/src/config/monitor.rs @@ -1,20 +1,19 @@ use std::path::PathBuf; -use std::sync::mpsc; -use std::time::Duration; +use std::sync::mpsc::{self, RecvTimeoutError}; +use std::time::{Duration, Instant}; use log::{debug, error}; -use notify_debouncer_mini::new_debouncer; -use notify_debouncer_mini::notify::RecursiveMode; +use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use winit::event_loop::EventLoopProxy; use alacritty_terminal::thread; use crate::event::{Event, EventType}; -#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] const DEBOUNCE_DELAY: Duration = Duration::from_millis(10); -#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] -const DEBOUNCE_DELAY: Duration = Duration::from_millis(1000); + +/// The fallback for `RecommendedWatcher` polling. +const FALLBACK_POLLING_TIMEOUT: Duration = Duration::from_secs(1); pub fn watch(mut paths: Vec, event_proxy: EventLoopProxy) { // Don't monitor config if there is no path to watch. @@ -41,8 +40,11 @@ pub fn watch(mut paths: Vec, event_proxy: EventLoopProxy) { // The Duration argument is a debouncing period. let (tx, rx) = mpsc::channel(); - let mut debouncer = match new_debouncer(DEBOUNCE_DELAY, None, tx) { - Ok(debouncer) => debouncer, + let mut watcher = match RecommendedWatcher::new( + tx, + Config::default().with_poll_interval(FALLBACK_POLLING_TIMEOUT), + ) { + Ok(watcher) => watcher, Err(err) => { error!("Unable to watch config file: {}", err); return; @@ -62,7 +64,6 @@ pub fn watch(mut paths: Vec, event_proxy: EventLoopProxy) { parents.sort_unstable(); parents.dedup(); - let watcher = debouncer.watcher(); // Watch all configuration file directories. for parent in &parents { if let Err(err) = watcher.watch(parent, RecursiveMode::NonRecursive) { @@ -70,24 +71,59 @@ pub fn watch(mut paths: Vec, event_proxy: EventLoopProxy) { } } + // The current debouncing time. + let mut debouncing_deadline: Option = None; + + // The events accumulated during the debounce period. + let mut received_events = Vec::new(); + loop { - let events = match rx.recv() { - Ok(Ok(events)) => events, + // We use `recv_timeout` to debounce the events coming from the watcher and reduce + // the amount of config reloads. + let event = match debouncing_deadline.as_ref() { + Some(debouncing_deadline) => { + rx.recv_timeout(debouncing_deadline.saturating_duration_since(Instant::now())) + }, + None => { + let event = rx.recv().map_err(Into::into); + // Set the debouncing deadline after receiving the event. + debouncing_deadline = Some(Instant::now() + DEBOUNCE_DELAY); + event + }, + }; + + match event { + Ok(Ok(event)) => match event.kind { + EventKind::Any + | EventKind::Create(_) + | EventKind::Modify(_) + | EventKind::Other => { + received_events.push(event); + }, + _ => (), + }, + Err(RecvTimeoutError::Timeout) => { + // Go back to polling the events. + debouncing_deadline = None; + + if received_events + .drain(..) + .flat_map(|event| event.paths.into_iter()) + .any(|path| paths.contains(&path)) + { + // Always reload the primary configuration file. + let event = Event::new(EventType::ConfigReload(paths[0].clone()), None); + let _ = event_proxy.send_event(event); + } + }, Ok(Err(err)) => { debug!("Config watcher errors: {:?}", err); - continue; }, Err(err) => { debug!("Config watcher channel dropped unexpectedly: {}", err); break; }, }; - - if events.iter().any(|e| paths.contains(&e.path)) { - // Always reload the primary configuration file. - let event = Event::new(EventType::ConfigReload(paths[0].clone()), None); - let _ = event_proxy.send_event(event); - } } }); }