rust 57 lines · 7 steps

Debouncing filesystem events in async Rust

A watcher collects rapid file changes and flushes them in one batch once the churn goes quiet.

Explained by highlit
1use std::collections::HashSet;
2use std::path::PathBuf;
3use std::time::Duration;
4 
5use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
6use tokio::sync::mpsc;
7use tokio::time::{sleep, Instant};
8 
9pub async fn watch_debounced(
10 root: PathBuf,
11 window: Duration,
12 mut on_flush: impl FnMut(HashSet<PathBuf>),
13) -> notify::Result<()> {
14 let (tx, mut rx) = mpsc::unbounded_channel::<Event>();
15 
16 let mut watcher = RecommendedWatcher::new(
17 move |res: notify::Result<Event>| {
18 if let Ok(event) = res {
19 let _ = tx.send(event);
20 }
21 },
22 notify::Config::default(),
23 )?;
24 watcher.watch(&root, RecursiveMode::Recursive)?;
25 
26 let mut pending: HashSet<PathBuf> = HashSet::new();
27 let mut deadline: Option<Instant> = None;
28 
29 loop {
30 let tick = async {
31 match deadline {
32 Some(at) => sleep(at.saturating_duration_since(Instant::now())).await,
33 None => std::future::pending().await,
34 }
35 };
36 
37 tokio::select! {
38 maybe_event = rx.recv() => {
39 match maybe_event {
40 Some(event) => {
41 pending.extend(event.paths);
42 deadline = Some(Instant::now() + window);
43 }
44 None => break,
45 }
46 }
47 _ = tick => {
48 if !pending.is_empty() {
49 on_flush(std::mem::take(&mut pending));
50 }
51 deadline = None;
52 }
53 }
54 }
55 
56 Ok(())
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Debouncing turns a storm of events into a single batch by resetting a deadline on every new arrival.
  2. 2A pending future lets tokio::select! wait indefinitely when there is no timer to race against.
  3. 3Bridging a callback-based API to async is a matter of forwarding events through a channel.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Debouncing filesystem events in async Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code