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/>.
*/
use std::{
path::Path,
time::{Duration, Instant},
};
use std::{path::Path, time::Duration};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use anyhow::Result;
use crossbeam::channel::Sender;
use crossbeam::{channel::Sender, select};
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<()> {
let config_dir = config_dir.to_path_buf();
let (debounce_tx, debounce_rx) = crossbeam::channel::unbounded();
std::thread::Builder::new()
.name("watcher".to_string())
.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(())
}
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 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");
watcher
@ -55,8 +61,6 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
info!("watching for changes in path: {:?}", config_dir);
let mut last_event_arrival = Instant::now();
loop {
let should_reload = match rx.recv() {
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
&& last_event_arrival.elapsed() > std::time::Duration::from_secs(WATCHER_DEBOUNCE_DURATION)
{
if let Err(error) = watcher_notify.send(()) {
error!("unable to send watcher file changed event: {}", error);
if should_reload {
if let Err(error) = debounce_tx.send(()) {
error!(
"unable to send watcher file changed event to debouncer: {}",
error
);
}
}
}
}
last_event_arrival = Instant::now();
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(()) {
error!("unable to send watcher file changed event: {}", error);
}
}
has_received_event = false;
},
}
}
}