Skip to content

Commit 385b639

Browse files
Add --removal-debounce for "remove" FS events, with a higher default
This should fix one pain point of mine when writing docs, running `cargo watch -x doc` and running `penguin serve target/doc`. Cargo first wipes the output directory clean and seem to only then start building the docs. There is quite a bit of time between the wipe and writing all the new files. This used to make Penguin already reload the sessions, resulting in 404 pages. Now that "remove" events are treated as less important, this should be fixed. I think it's generally a valid assumption that a quick reload after only a deletion event is rarely the intended behavior.
1 parent 3104171 commit 385b639

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

app/src/args.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub(crate) enum Command {
8585
Reload,
8686
}
8787

88-
#[derive(Debug, StructOpt)]
88+
#[derive(Debug, Clone, StructOpt)]
8989
pub(crate) struct ServeOptions {
9090
/// Mount a directory on an URI path: '--mount <uri>:<path>'.
9191
///
@@ -126,6 +126,21 @@ pub(crate) struct ServeOptions {
126126
parse(try_from_str = parse_duration)
127127
)]
128128
pub(crate) debounce_duration: Duration,
129+
130+
/// The debounce duration (in ms) for when a file in a watched path was
131+
/// removed.
132+
///
133+
/// Like `--debounce`, but for "remove" file system events. This is treated
134+
/// separately as usually reloading quickly on deletion is not useful: the
135+
/// reload would result in a 404 page. And in many situation (e.g. cargo
136+
/// doc) the watched directory is first wiped by a build process and then
137+
/// populated again after a while. So this default is much higher.
138+
#[structopt(
139+
long = "--removal-debounce",
140+
default_value = "3000",
141+
parse(try_from_str = parse_duration)
142+
)]
143+
pub(crate) removal_debounce_duration: Duration,
129144
}
130145

131146
fn parse_mount(s: &str) -> Result<Mount, &'static str> {

app/src/server.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{env, ops::Deref, path::Path, thread, time::Duration};
22

33
use anyhow::{Context, Result};
44
use log::{debug, info, trace, LevelFilter};
5+
use notify::Op;
56
use penguin::{Config, Controller, Mount, ProxyTarget, Server};
67

78
use crate::args::{Args, DEFAULT_PORT, ServeOptions};
@@ -44,7 +45,7 @@ pub(crate) async fn run(
4445
};
4546

4647
if !watched_paths.is_empty() {
47-
watch(controller, options.debounce_duration, &watched_paths)?;
48+
watch(controller, options, &watched_paths)?;
4849
}
4950

5051
// Nice output of what is being done
@@ -89,7 +90,7 @@ pub(crate) async fn run(
8990

9091
fn watch<'a>(
9192
controller: Controller,
92-
debounce_duration: Duration,
93+
options: &ServeOptions,
9394
paths: &[&Path],
9495
) -> Result<()> {
9596
use std::sync::mpsc::{channel, RecvTimeoutError};
@@ -114,13 +115,24 @@ fn watch<'a>(
114115

115116
// We create a new thread that will react to incoming events and trigger a
116117
// page reload.
118+
let options = options.clone();
117119
thread::spawn(move || {
118120
// Move it to the thread to avoid dropping it early.
119121
let _watcher = watcher;
122+
let debounce_duration_of = |event: &RawEvent| {
123+
if event.op.as_ref().is_ok_and(|&op| op == Op::REMOVE) {
124+
options.removal_debounce_duration
125+
} else {
126+
options.debounce_duration
127+
}
128+
};
120129

121130
while let Ok(event) = rx.recv() {
131+
let mut debounce_duration = debounce_duration_of(&event);
132+
122133
debug!(
123-
"Received watch-event for '{}'. Debouncing now for {:?}.",
134+
"Received watch-event '{:?}' for '{}'. Debouncing now for {:?}.",
135+
event.op,
124136
pretty_path(&event),
125137
debounce_duration,
126138
);
@@ -129,7 +141,21 @@ fn watch<'a>(
129141
// `debounce_duration`.
130142
loop {
131143
match rx.recv_timeout(debounce_duration) {
132-
Ok(event) => trace!("Debounce interrupted by '{}'", pretty_path(&event)),
144+
Ok(event) => {
145+
trace!(
146+
"Debounce interrupted by '{:?}' of '{}'",
147+
event.op,
148+
pretty_path(&event),
149+
);
150+
151+
// We reset the waiting duration to the minimum of both
152+
// events' durations. So if any non-remove event is
153+
// involved, the shorter duration is used.
154+
debounce_duration = std::cmp::min(
155+
debounce_duration_of(&event),
156+
debounce_duration,
157+
);
158+
},
133159
Err(RecvTimeoutError::Timeout) => break,
134160
Err(RecvTimeoutError::Disconnected) => return,
135161
}

0 commit comments

Comments
 (0)