Skip to content

Commit bbcadcb

Browse files
committed
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
1 parent 8cdb023 commit bbcadcb

File tree

3 files changed

+80
-28
lines changed

3 files changed

+80
-28
lines changed

Diff for: src/args.rs

+23-12
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::bug_report;
22
use anyhow::{anyhow, Result};
33
use asyncgit::sync::RepoPath;
44
use clap::{
5-
crate_authors, crate_description, crate_name, crate_version, Arg,
6-
Command as ClapApp,
5+
crate_authors, crate_description, crate_name, crate_version,
6+
value_parser, Arg, Command as ClapApp,
77
};
88
use simplelog::{Config, LevelFilter, WriteLogger};
99
use std::{
@@ -15,6 +15,7 @@ use std::{
1515
pub struct CliArgs {
1616
pub theme: PathBuf,
1717
pub repo_path: RepoPath,
18+
pub poll_watcher: bool,
1819
}
1920

2021
pub fn process_cmdline() -> Result<CliArgs> {
@@ -46,17 +47,20 @@ pub fn process_cmdline() -> Result<CliArgs> {
4647
.get_one::<String>("theme")
4748
.map_or_else(|| PathBuf::from("theme.ron"), PathBuf::from);
4849

49-
if get_app_config_path()?.join(&arg_theme).is_file() {
50-
Ok(CliArgs {
51-
theme: get_app_config_path()?.join(arg_theme),
52-
repo_path,
53-
})
50+
let theme = if get_app_config_path()?.join(&arg_theme).is_file() {
51+
get_app_config_path()?.join(arg_theme)
5452
} else {
55-
Ok(CliArgs {
56-
theme: get_app_config_path()?.join("theme.ron"),
57-
repo_path,
58-
})
59-
}
53+
get_app_config_path()?.join("theme.ron")
54+
};
55+
56+
let arg_poll: bool =
57+
*arg_matches.get_one("poll").unwrap_or(&false);
58+
59+
Ok(CliArgs {
60+
theme,
61+
poll_watcher: arg_poll,
62+
repo_path,
63+
})
6064
}
6165

6266
fn app() -> ClapApp {
@@ -90,6 +94,13 @@ fn app() -> ClapApp {
9094
.long("logging")
9195
.num_args(0),
9296
)
97+
.arg(
98+
Arg::new("poll")
99+
.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")
100+
.long("polling")
101+
.num_args(0)
102+
.value_parser(value_parser!(bool)),
103+
)
93104
.arg(
94105
Arg::new("bugreport")
95106
.help("Generate a bug report")

Diff for: src/main.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ fn main() -> Result<()> {
150150
theme,
151151
key_config.clone(),
152152
&input,
153+
cliargs.poll_watcher,
153154
&mut terminal,
154155
)?;
155156

@@ -170,13 +171,17 @@ fn run_app(
170171
theme: Theme,
171172
key_config: KeyConfig,
172173
input: &Input,
174+
poll_watcher: bool,
173175
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
174176
) -> Result<QuitState, anyhow::Error> {
175177
let (tx_git, rx_git) = unbounded();
176178
let (tx_app, rx_app) = unbounded();
177179

178180
let rx_input = input.receiver();
179-
let watcher = RepoWatcher::new(repo_work_dir(&repo)?.as_str())?;
181+
let watcher = RepoWatcher::new(
182+
repo_work_dir(&repo)?.as_str(),
183+
poll_watcher,
184+
);
180185
let rx_watcher = watcher.receiver();
181186
let spinner_ticker = tick(SPINNER_INTERVAL);
182187

Diff for: src/watcher.rs

+51-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use anyhow::Result;
22
use crossbeam_channel::{unbounded, Sender};
3-
use notify::{Error, RecommendedWatcher, RecursiveMode};
3+
use notify::{
4+
Config, Error, PollWatcher, RecommendedWatcher, RecursiveMode,
5+
Watcher,
6+
};
47
use notify_debouncer_mini::{
5-
new_debouncer, DebouncedEvent, Debouncer,
8+
new_debouncer, new_debouncer_opt, DebouncedEvent,
69
};
710
use scopetime::scope_time;
811
use std::{
@@ -11,22 +14,23 @@ use std::{
1114

1215
pub struct RepoWatcher {
1316
receiver: crossbeam_channel::Receiver<()>,
14-
#[allow(dead_code)]
15-
debouncer: Debouncer<RecommendedWatcher>,
1617
}
1718

1819
impl RepoWatcher {
19-
pub fn new(workdir: &str) -> Result<Self> {
20-
scope_time!("RepoWatcher::new");
20+
pub fn new(workdir: &str, poll: bool) -> Self {
21+
log::trace!(
22+
"poll watcher: {poll} recommended: {:?}",
23+
RecommendedWatcher::kind()
24+
);
2125

2226
let (tx, rx) = std::sync::mpsc::channel();
2327

24-
let mut debouncer =
25-
new_debouncer(Duration::from_secs(2), None, tx)?;
28+
let workdir = workdir.to_string();
2629

27-
debouncer
28-
.watcher()
29-
.watch(Path::new(workdir), RecursiveMode::Recursive)?;
30+
thread::spawn(move || {
31+
let timeout = Duration::from_secs(2);
32+
create_watcher(poll, timeout, tx, &workdir);
33+
});
3034

3135
let (out_tx, out_rx) = unbounded();
3236

@@ -37,10 +41,7 @@ impl RepoWatcher {
3741
}
3842
});
3943

40-
Ok(Self {
41-
debouncer,
42-
receiver: out_rx,
43-
})
44+
Self { receiver: out_rx }
4445
}
4546

4647
///
@@ -71,3 +72,38 @@ impl RepoWatcher {
7172
}
7273
}
7374
}
75+
76+
fn create_watcher(
77+
poll: bool,
78+
timeout: Duration,
79+
tx: std::sync::mpsc::Sender<
80+
Result<Vec<DebouncedEvent>, Vec<Error>>,
81+
>,
82+
workdir: &str,
83+
) {
84+
scope_time!("create_watcher");
85+
86+
if poll {
87+
let config = Config::default()
88+
.with_poll_interval(Duration::from_secs(2));
89+
let mut bouncer = new_debouncer_opt::<_, PollWatcher>(
90+
timeout, None, tx, config,
91+
)
92+
.expect("Watch create error");
93+
bouncer
94+
.watcher()
95+
.watch(Path::new(&workdir), RecursiveMode::Recursive)
96+
.expect("Watch error");
97+
98+
std::mem::forget(bouncer);
99+
} else {
100+
let mut bouncer = new_debouncer(timeout, None, tx)
101+
.expect("Watch create error");
102+
bouncer
103+
.watcher()
104+
.watch(Path::new(&workdir), RecursiveMode::Recursive)
105+
.expect("Watch error");
106+
107+
std::mem::forget(bouncer);
108+
};
109+
}

0 commit comments

Comments
 (0)