threaded watcher creation and arg for config

new argument `polling` allows to force watcher to use polling instead of file system events.
this should address the issue in #1436 and maybe even #1437
This commit is contained in:
extrawurst 2022-11-21 18:09:08 +01:00
parent 8cdb02349f
commit bbcadcb5d1
3 changed files with 80 additions and 28 deletions

View file

@ -2,8 +2,8 @@ use crate::bug_report;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use asyncgit::sync::RepoPath; use asyncgit::sync::RepoPath;
use clap::{ use clap::{
crate_authors, crate_description, crate_name, crate_version, Arg, crate_authors, crate_description, crate_name, crate_version,
Command as ClapApp, value_parser, Arg, Command as ClapApp,
}; };
use simplelog::{Config, LevelFilter, WriteLogger}; use simplelog::{Config, LevelFilter, WriteLogger};
use std::{ use std::{
@ -15,6 +15,7 @@ use std::{
pub struct CliArgs { pub struct CliArgs {
pub theme: PathBuf, pub theme: PathBuf,
pub repo_path: RepoPath, pub repo_path: RepoPath,
pub poll_watcher: bool,
} }
pub fn process_cmdline() -> Result<CliArgs> { pub fn process_cmdline() -> Result<CliArgs> {
@ -46,17 +47,20 @@ pub fn process_cmdline() -> Result<CliArgs> {
.get_one::<String>("theme") .get_one::<String>("theme")
.map_or_else(|| PathBuf::from("theme.ron"), PathBuf::from); .map_or_else(|| PathBuf::from("theme.ron"), PathBuf::from);
if get_app_config_path()?.join(&arg_theme).is_file() { let theme = if get_app_config_path()?.join(&arg_theme).is_file() {
Ok(CliArgs { get_app_config_path()?.join(arg_theme)
theme: get_app_config_path()?.join(arg_theme),
repo_path,
})
} else { } else {
Ok(CliArgs { get_app_config_path()?.join("theme.ron")
theme: get_app_config_path()?.join("theme.ron"), };
repo_path,
}) let arg_poll: bool =
} *arg_matches.get_one("poll").unwrap_or(&false);
Ok(CliArgs {
theme,
poll_watcher: arg_poll,
repo_path,
})
} }
fn app() -> ClapApp { fn app() -> ClapApp {
@ -90,6 +94,13 @@ fn app() -> ClapApp {
.long("logging") .long("logging")
.num_args(0), .num_args(0),
) )
.arg(
Arg::new("poll")
.help("Poll folder for changes instead of using file system events. This can be useful if you run into issues with maximum # of file descriptors")
.long("polling")
.num_args(0)
.value_parser(value_parser!(bool)),
)
.arg( .arg(
Arg::new("bugreport") Arg::new("bugreport")
.help("Generate a bug report") .help("Generate a bug report")

View file

@ -150,6 +150,7 @@ fn main() -> Result<()> {
theme, theme,
key_config.clone(), key_config.clone(),
&input, &input,
cliargs.poll_watcher,
&mut terminal, &mut terminal,
)?; )?;
@ -170,13 +171,17 @@ fn run_app(
theme: Theme, theme: Theme,
key_config: KeyConfig, key_config: KeyConfig,
input: &Input, input: &Input,
poll_watcher: bool,
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
) -> Result<QuitState, anyhow::Error> { ) -> Result<QuitState, anyhow::Error> {
let (tx_git, rx_git) = unbounded(); let (tx_git, rx_git) = unbounded();
let (tx_app, rx_app) = unbounded(); let (tx_app, rx_app) = unbounded();
let rx_input = input.receiver(); let rx_input = input.receiver();
let watcher = RepoWatcher::new(repo_work_dir(&repo)?.as_str())?; let watcher = RepoWatcher::new(
repo_work_dir(&repo)?.as_str(),
poll_watcher,
);
let rx_watcher = watcher.receiver(); let rx_watcher = watcher.receiver();
let spinner_ticker = tick(SPINNER_INTERVAL); let spinner_ticker = tick(SPINNER_INTERVAL);

View file

@ -1,8 +1,11 @@
use anyhow::Result; use anyhow::Result;
use crossbeam_channel::{unbounded, Sender}; use crossbeam_channel::{unbounded, Sender};
use notify::{Error, RecommendedWatcher, RecursiveMode}; use notify::{
Config, Error, PollWatcher, RecommendedWatcher, RecursiveMode,
Watcher,
};
use notify_debouncer_mini::{ use notify_debouncer_mini::{
new_debouncer, DebouncedEvent, Debouncer, new_debouncer, new_debouncer_opt, DebouncedEvent,
}; };
use scopetime::scope_time; use scopetime::scope_time;
use std::{ use std::{
@ -11,22 +14,23 @@ use std::{
pub struct RepoWatcher { pub struct RepoWatcher {
receiver: crossbeam_channel::Receiver<()>, receiver: crossbeam_channel::Receiver<()>,
#[allow(dead_code)]
debouncer: Debouncer<RecommendedWatcher>,
} }
impl RepoWatcher { impl RepoWatcher {
pub fn new(workdir: &str) -> Result<Self> { pub fn new(workdir: &str, poll: bool) -> Self {
scope_time!("RepoWatcher::new"); log::trace!(
"poll watcher: {poll} recommended: {:?}",
RecommendedWatcher::kind()
);
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let mut debouncer = let workdir = workdir.to_string();
new_debouncer(Duration::from_secs(2), None, tx)?;
debouncer thread::spawn(move || {
.watcher() let timeout = Duration::from_secs(2);
.watch(Path::new(workdir), RecursiveMode::Recursive)?; create_watcher(poll, timeout, tx, &workdir);
});
let (out_tx, out_rx) = unbounded(); let (out_tx, out_rx) = unbounded();
@ -37,10 +41,7 @@ impl RepoWatcher {
} }
}); });
Ok(Self { Self { receiver: out_rx }
debouncer,
receiver: out_rx,
})
} }
/// ///
@ -71,3 +72,38 @@ impl RepoWatcher {
} }
} }
} }
fn create_watcher(
poll: bool,
timeout: Duration,
tx: std::sync::mpsc::Sender<
Result<Vec<DebouncedEvent>, Vec<Error>>,
>,
workdir: &str,
) {
scope_time!("create_watcher");
if poll {
let config = Config::default()
.with_poll_interval(Duration::from_secs(2));
let mut bouncer = new_debouncer_opt::<_, PollWatcher>(
timeout, None, tx, config,
)
.expect("Watch create error");
bouncer
.watcher()
.watch(Path::new(&workdir), RecursiveMode::Recursive)
.expect("Watch error");
std::mem::forget(bouncer);
} else {
let mut bouncer = new_debouncer(timeout, None, tx)
.expect("Watch create error");
bouncer
.watcher()
.watch(Path::new(&workdir), RecursiveMode::Recursive)
.expect("Watch error");
std::mem::forget(bouncer);
};
}