fix(core): fix config watcher debouncing mechanism. Fix #769
This commit is contained in:
		
							parent
							
								
									d1ebcf62c8
								
							
						
					
					
						commit
						2c25d60e87
					
				| 
						 | 
					@ -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: {}",
 | 
				
			||||||
      if let Err(error) = watcher_notify.send(()) {
 | 
					          error
 | 
				
			||||||
        error!("unable to send watcher file changed event: {}", 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;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user