Skip to content

Commit

Permalink
Fix ordering of debounced events
Browse files Browse the repository at this point in the history
  • Loading branch information
dfaust committed Sep 14, 2024
1 parent 128bf62 commit 3ec149f
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ v5 maintenance branch is on `v5_maintenance` after `5.2.0`

v4 commits split out to branch `v4_maintenance` starting with `4.0.16`

## debouncer-full 0.4.1 (unreleased)

- FIX: ordering of debounced events could lead to a panic with Rust 1.81.0 and above [#636]

[#636]: https://github.com/notify-rs/notify/issues/636

## notify 7.0.0

- CHANGE: raise MSRV to 1.72
Expand Down
49 changes: 37 additions & 12 deletions notify-debouncer-full/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {

self.queues = queues_remaining;

// order events for different files chronologically, but keep the order of events for the same file
events_expired.sort_by(|event_a, event_b| {
// use the last path because rename events are emitted for the target path
if event_a.paths.last() == event_b.paths.last() {
std::cmp::Ordering::Equal
} else {
event_a.time.cmp(&event_b.time)
}
});

events_expired
sort_events(events_expired)
}

/// Returns all currently stored errors
Expand Down Expand Up @@ -713,6 +703,39 @@ pub fn new_debouncer<F: DebounceEventHandler>(
)
}

fn sort_events(events: Vec<DebouncedEvent>) -> Vec<DebouncedEvent> {
let mut sorted = Vec::with_capacity(events.len());

// group events by path
let mut events_by_path: HashMap<_, VecDeque<_>> =
events.into_iter().fold(HashMap::new(), |mut acc, event| {
acc.entry(event.paths.last().cloned().unwrap_or_default())
.or_default()
.push_back(event);
acc
});

// push events for different paths in chronological order and keep the order of events with the same path
while !events_by_path.is_empty() {
let min_time = events_by_path
.values()
.map(|events| events[0].time)
.min()
.unwrap();

for events in events_by_path.values_mut() {
while events.front().is_some_and(|event| event.time <= min_time) {
let event = events.pop_front().unwrap();
sorted.push(event);
}
}

events_by_path.retain(|_, events| !events.is_empty());
}

sorted
}

#[cfg(test)]
mod tests {
use std::{fs, path::Path};
Expand Down Expand Up @@ -761,7 +784,9 @@ mod tests {
"emit_close_events_only_once",
"emit_modify_event_after_close_event",
"emit_needs_rescan_event",
"read_file_id_without_create_event"
"read_file_id_without_create_event",
"sort_events_chronologically",
"sort_events_with_reordering"
)]
file_name: &str,
) {
Expand Down
42 changes: 42 additions & 0 deletions notify-debouncer-full/test_cases/sort_events_chronologically.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
state: {
queues: {
/watch/file-1: {
events: [
{ kind: "create-any", paths: ["*"], time: 2 }
{ kind: "modify-any", paths: ["*"], time: 3 }
]
}
/watch/file-2: {
events: [
{ kind: "create-any", paths: ["*"], time: 1 }
{ kind: "modify-any", paths: ["*"], time: 4 }
]
}
}
}
expected: {
queues: {
/watch/file-1: {
events: [
{ kind: "create-any", paths: ["*"], time: 2 }
{ kind: "modify-any", paths: ["*"], time: 3 }
]
}
/watch/file-2: {
events: [
{ kind: "create-any", paths: ["*"], time: 1 }
{ kind: "modify-any", paths: ["*"], time: 4 }
]
}
}
events: {
long: [
{ kind: "create-any", paths: ["/watch/file-2"], time: 1 }
{ kind: "create-any", paths: ["/watch/file-1"], time: 2 }
{ kind: "modify-any", paths: ["/watch/file-1"], time: 3 }
{ kind: "modify-any", paths: ["/watch/file-2"], time: 4 }
]
}
}
}
42 changes: 42 additions & 0 deletions notify-debouncer-full/test_cases/sort_events_with_reordering.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
state: {
queues: {
/watch/file-1: {
events: [
{ kind: "create-any", paths: ["*"], time: 2 }
{ kind: "modify-any", paths: ["*"], time: 3 }
]
}
/watch/file-2: {
events: [
{ kind: "rename-to", paths: ["*"], time: 4 }
{ kind: "modify-any", paths: ["*"], time: 1 }
]
}
}
}
expected: {
queues: {
/watch/file-1: {
events: [
{ kind: "create-any", paths: ["*"], time: 2 }
{ kind: "modify-any", paths: ["*"], time: 3 }
]
}
/watch/file-2: {
events: [
{ kind: "rename-to", paths: ["*"], time: 4 }
{ kind: "modify-any", paths: ["*"], time: 1 }
]
}
}
events: {
long: [
{ kind: "create-any", paths: ["/watch/file-1"], time: 2 }
{ kind: "modify-any", paths: ["/watch/file-1"], time: 3 }
{ kind: "rename-to", paths: ["/watch/file-2"], time: 4 }
{ kind: "modify-any", paths: ["/watch/file-2"], time: 1 }
]
}
}
}

0 comments on commit 3ec149f

Please sign in to comment.