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.
This commit is contained in:
Kirill Chibisov 2023-02-02 13:13:23 +03:00 committed by GitHub
parent 3354203e57
commit 799d8f75bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 33 deletions

32
Cargo.lock generated
View File

@ -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"

View File

@ -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 }

View File

@ -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<PathBuf>, event_proxy: EventLoopProxy<Event>) {
// Don't monitor config if there is no path to watch.
@ -41,8 +40,11 @@ pub fn watch(mut paths: Vec<PathBuf>, event_proxy: EventLoopProxy<Event>) {
// 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<PathBuf>, event_proxy: EventLoopProxy<Event>) {
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<PathBuf>, event_proxy: EventLoopProxy<Event>) {
}
}
// The current debouncing time.
let mut debouncing_deadline: Option<Instant> = 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);
}
}
});
}