Skip to content

Commit 1fc49b3

Browse files
committed
fix: use trailing-edge debounce for file watcher
1 parent 62ba417 commit 1fc49b3

4 files changed

Lines changed: 39 additions & 64 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ chrono = "0.4"
5757
ipnet = "2"
5858
tower-http = { version = "0.6.6", features = ["fs"] }
5959
tower = { version = "0.5.2", features = ["util"] }
60-
ureq = { version = "2.12.1", default-features = false, features = ["tls"] }
6160
brotli = "8"
6261
http_encoding_headers = "0.2.0"
6362
headers = "0.4.1"
@@ -69,7 +68,6 @@ syntect = "5.3.0"
6968
syntect-assets = "0.23.6"
7069
pulldown-cmark = "0.12.2"
7170
notify = "8"
72-
notify-debouncer-mini = "0.6"
7371

7472
[dependencies.cross-stream]
7573
version = "0.9.0"

src/main.rs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -160,46 +160,47 @@ fn spawn_file_watcher(script_path: PathBuf, tx: mpsc::Sender<String>) {
160160
.watch(&watch_dir, RecursiveMode::Recursive)
161161
.expect("Failed to watch directory");
162162

163-
// Keep watcher alive
164-
let _watcher = watcher;
165-
166-
// Set to past time so first event isn't debounced
167-
let mut last_reload = std::time::Instant::now() - Duration::from_secs(1);
168163
let debounce = Duration::from_millis(100);
164+
let mut pending_reload = false;
165+
166+
loop {
167+
let timeout = if pending_reload {
168+
debounce
169+
} else {
170+
Duration::from_secs(86400)
171+
};
169172

170-
for result in raw_rx {
171-
match result {
172-
Ok(event) => {
173-
// Only react to modifications, not access/open events
173+
match raw_rx.recv_timeout(timeout) {
174+
Ok(Ok(event)) => {
174175
use notify::EventKind;
175-
let is_modification = matches!(
176+
let dominated_by = matches!(
176177
event.kind,
177178
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_)
178179
);
179-
if !is_modification {
180-
continue;
181-
}
182-
183-
// Debounce rapid events
184-
if last_reload.elapsed() < debounce {
185-
continue;
180+
if dominated_by {
181+
pending_reload = true;
186182
}
187-
last_reload = std::time::Instant::now();
188-
189-
// Re-read and send the script file
190-
match std::fs::read_to_string(&script_path) {
191-
Ok(content) => {
192-
if tx.blocking_send(content).is_err() {
193-
break;
183+
}
184+
Ok(Err(e)) => {
185+
eprintln!("Watch error: {e:?}");
186+
}
187+
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
188+
if pending_reload {
189+
pending_reload = false;
190+
match std::fs::read_to_string(&script_path) {
191+
Ok(content) => {
192+
if tx.blocking_send(content).is_err() {
193+
break;
194+
}
195+
}
196+
Err(e) => {
197+
eprintln!("Error reading script file: {e}");
194198
}
195-
}
196-
Err(e) => {
197-
eprintln!("Error reading script file: {e}");
198199
}
199200
}
200201
}
201-
Err(e) => {
202-
eprintln!("Watch error: {e:?}");
202+
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
203+
break;
203204
}
204205
}
205206
}

tests/server_test.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,10 +1435,15 @@ async fn test_watch_file_reload_on_change() {
14351435
.expect("curl failed");
14361436
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "version1");
14371437

1438-
// Modify the script file
1438+
// Trigger a spurious event, then write the actual change.
1439+
// This tests trailing-edge debounce: the reload should wait for events to
1440+
// settle and read the final content, not the content at the first event.
1441+
let dummy_path = tmp.path().join("trigger.txt");
1442+
std::fs::write(&dummy_path, "trigger").unwrap();
1443+
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
14391444
std::fs::write(&script_path, r#"{|req| "version2"}"#).unwrap();
14401445

1441-
// Wait for file watcher to detect change and reload
1446+
// Wait for debounced reload to complete
14421447
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
14431448

14441449
// Verify updated response

0 commit comments

Comments
 (0)