fix(core): fix config watcher debouncing mechanism. Fix #769

This commit is contained in:
Federico Terzi 2021-10-10 22:36:46 +02:00
parent d1ebcf62c8
commit 2c25d60e87

View File

@ -17,36 +17,42 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{ use std::{path::Path, time::Duration};
path::Path,
time::{Duration, Instant},
};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use anyhow::Result; use anyhow::Result;
use crossbeam::channel::Sender; use crossbeam::{channel::Sender, select};
use log::{error, info, warn}; use log::{error, info, warn};
const WATCHER_DEBOUNCE_DURATION: u64 = 1; const WATCHER_NOTIFY_DELAY_MS: u64 = 500;
const WATCHER_DEBOUNCE_DURATION_MS: u64 = 1000;
pub fn initialize_and_spawn(config_dir: &Path, watcher_notify: Sender<()>) -> Result<()> { pub fn initialize_and_spawn(config_dir: &Path, watcher_notify: Sender<()>) -> Result<()> {
let config_dir = config_dir.to_path_buf(); let config_dir = config_dir.to_path_buf();
let (debounce_tx, debounce_rx) = crossbeam::channel::unbounded();
std::thread::Builder::new() std::thread::Builder::new()
.name("watcher".to_string()) .name("watcher".to_string())
.spawn(move || { .spawn(move || {
watcher_main(&config_dir, &watcher_notify); watcher_main(&config_dir, debounce_tx);
})?;
std::thread::Builder::new()
.name("watcher-debouncer".to_string())
.spawn(move || {
debouncer_main(debounce_rx, &watcher_notify);
})?; })?;
Ok(()) Ok(())
} }
fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) { fn watcher_main(config_dir: &Path, debounce_tx: crossbeam::channel::Sender<()>) {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let mut watcher: RecommendedWatcher = let mut watcher: RecommendedWatcher =
Watcher::new(tx, Duration::from_secs(WATCHER_DEBOUNCE_DURATION)) Watcher::new(tx, Duration::from_millis(WATCHER_NOTIFY_DELAY_MS))
.expect("unable to create file watcher"); .expect("unable to create file watcher");
watcher watcher
@ -55,8 +61,6 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
info!("watching for changes in path: {:?}", config_dir); info!("watching for changes in path: {:?}", config_dir);
let mut last_event_arrival = Instant::now();
loop { loop {
let should_reload = match rx.recv() { let should_reload = match rx.recv() {
Ok(event) => { Ok(event) => {
@ -92,16 +96,35 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
} }
}; };
// Send only one event, otherwise we could run the risk of useless reloads or even race conditions. if should_reload {
if should_reload if let Err(error) = debounce_tx.send(()) {
&& last_event_arrival.elapsed() > std::time::Duration::from_secs(WATCHER_DEBOUNCE_DURATION) error!(
{ "unable to send watcher file changed event to debouncer: {}",
error
);
}
}
}
}
fn debouncer_main(debounce_rx: crossbeam::channel::Receiver<()>, watcher_notify: &Sender<()>) {
let mut has_received_event = false;
loop {
select! {
recv(debounce_rx) -> _ => {
has_received_event = true;
},
default(Duration::from_millis(WATCHER_DEBOUNCE_DURATION_MS)) => {
if has_received_event {
if let Err(error) = watcher_notify.send(()) { if let Err(error) = watcher_notify.send(()) {
error!("unable to send watcher file changed event: {}", error); error!("unable to send watcher file changed event: {}", error);
} }
} }
last_event_arrival = Instant::now(); has_received_event = false;
},
}
} }
} }