Skip to content

Commit 2bef540

Browse files
authored
Merge pull request #643 from dfaust/release-debouncer-full-0.3.2
Prepare release of debouncer full 0.3.2
2 parents 4a00121 + ef8bc72 commit 2bef540

File tree

5 files changed

+142
-16
lines changed

5 files changed

+142
-16
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
v5 maintenance branch is on `v5_maintenance` after `5.2.0`
44
v4 commits split out to branch `v4_maintenance` starting with `4.0.16`
55

6+
## debouncer-full 0.3.2 (2024-09-29)
7+
8+
- FIX: ordering of debounced events could lead to a panic with Rust 1.81.0 and above [#636]
9+
10+
[#636]: https://github.com/notify-rs/notify/issues/636
11+
612
## debouncer-full 0.3.1 (2023-08-21)
713

814
- CHANGE: remove serde binary experiment opt-out after it got removed [#530]

notify-debouncer-full/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "notify-debouncer-full"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
edition = "2021"
55
rust-version = "1.60"
66
description = "notify event debouncer optimized for ease of use"

notify-debouncer-full/src/lib.rs

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@
5757
//! - `crossbeam` enabled by default, adds [`DebounceEventHandler`](DebounceEventHandler) support for crossbeam channels.
5858
//! Also enables crossbeam-channel in the re-exported notify. You may want to disable this when using the tokio async runtime.
5959
//! - `serde` enables serde support for events.
60-
//!
60+
//!
6161
//! # Caveats
62-
//!
62+
//!
6363
//! As all file events are sourced from notify, the [known problems](https://docs.rs/notify/latest/notify/#known-problems) section applies here too.
6464
6565
mod cache;
@@ -69,7 +69,8 @@ mod debounced_event;
6969
mod testing;
7070

7171
use std::{
72-
collections::{HashMap, VecDeque},
72+
cmp::Reverse,
73+
collections::{BinaryHeap, HashMap, VecDeque},
7374
path::PathBuf,
7475
sync::{
7576
atomic::{AtomicBool, Ordering},
@@ -249,17 +250,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
249250

250251
self.queues = queues_remaining;
251252

252-
// order events for different files chronologically, but keep the order of events for the same file
253-
events_expired.sort_by(|event_a, event_b| {
254-
// use the last path because rename events are emitted for the target path
255-
if event_a.paths.last() == event_b.paths.last() {
256-
std::cmp::Ordering::Equal
257-
} else {
258-
event_a.time.cmp(&event_b.time)
259-
}
260-
});
261-
262-
events_expired
253+
sort_events(events_expired)
263254
}
264255

265256
/// Returns all currently stored errors
@@ -654,6 +645,49 @@ pub fn new_debouncer<F: DebounceEventHandler>(
654645
)
655646
}
656647

648+
fn sort_events(events: Vec<DebouncedEvent>) -> Vec<DebouncedEvent> {
649+
let mut sorted = Vec::with_capacity(events.len());
650+
651+
// group events by path
652+
let mut events_by_path: HashMap<_, VecDeque<_>> =
653+
events.into_iter().fold(HashMap::new(), |mut acc, event| {
654+
acc.entry(event.paths.last().cloned().unwrap_or_default())
655+
.or_default()
656+
.push_back(event);
657+
acc
658+
});
659+
660+
// push events for different paths in chronological order and keep the order of events with the same path
661+
662+
let mut min_time_heap = events_by_path
663+
.iter()
664+
.map(|(path, events)| Reverse((events[0].time, path.clone())))
665+
.collect::<BinaryHeap<_>>();
666+
667+
while let Some(Reverse((min_time, path))) = min_time_heap.pop() {
668+
// unwrap is safe because only paths from `events_by_path` are added to `min_time_heap`
669+
// and they are never removed from `events_by_path`.
670+
let events = events_by_path.get_mut(&path).unwrap();
671+
672+
let mut push_next = false;
673+
674+
while events.front().map_or(false, |event| event.time <= min_time) {
675+
// unwrap is safe beause `pop_front` mus return some in order to enter the loop
676+
let event = events.pop_front().unwrap();
677+
sorted.push(event);
678+
push_next = true;
679+
}
680+
681+
if push_next {
682+
if let Some(event) = events.front() {
683+
min_time_heap.push(Reverse((event.time, path)));
684+
}
685+
}
686+
}
687+
688+
sorted
689+
}
690+
657691
#[cfg(test)]
658692
mod tests {
659693
use std::{fs, path::Path};
@@ -702,7 +736,9 @@ mod tests {
702736
"emit_close_events_only_once",
703737
"emit_modify_event_after_close_event",
704738
"emit_needs_rescan_event",
705-
"read_file_id_without_create_event"
739+
"read_file_id_without_create_event",
740+
"sort_events_chronologically",
741+
"sort_events_with_reordering"
706742
)]
707743
file_name: &str,
708744
) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
state: {
3+
queues: {
4+
/watch/file-1: {
5+
events: [
6+
{ kind: "create-any", paths: ["*"], time: 2 }
7+
{ kind: "modify-any", paths: ["*"], time: 3 }
8+
]
9+
}
10+
/watch/file-2: {
11+
events: [
12+
{ kind: "create-any", paths: ["*"], time: 1 }
13+
{ kind: "modify-any", paths: ["*"], time: 4 }
14+
]
15+
}
16+
}
17+
}
18+
expected: {
19+
queues: {
20+
/watch/file-1: {
21+
events: [
22+
{ kind: "create-any", paths: ["*"], time: 2 }
23+
{ kind: "modify-any", paths: ["*"], time: 3 }
24+
]
25+
}
26+
/watch/file-2: {
27+
events: [
28+
{ kind: "create-any", paths: ["*"], time: 1 }
29+
{ kind: "modify-any", paths: ["*"], time: 4 }
30+
]
31+
}
32+
}
33+
events: {
34+
long: [
35+
{ kind: "create-any", paths: ["/watch/file-2"], time: 1 }
36+
{ kind: "create-any", paths: ["/watch/file-1"], time: 2 }
37+
{ kind: "modify-any", paths: ["/watch/file-1"], time: 3 }
38+
{ kind: "modify-any", paths: ["/watch/file-2"], time: 4 }
39+
]
40+
}
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
state: {
3+
queues: {
4+
/watch/file-1: {
5+
events: [
6+
{ kind: "create-any", paths: ["*"], time: 2 }
7+
{ kind: "modify-any", paths: ["*"], time: 3 }
8+
]
9+
}
10+
/watch/file-2: {
11+
events: [
12+
{ kind: "rename-to", paths: ["*"], time: 4 }
13+
{ kind: "modify-any", paths: ["*"], time: 1 }
14+
]
15+
}
16+
}
17+
}
18+
expected: {
19+
queues: {
20+
/watch/file-1: {
21+
events: [
22+
{ kind: "create-any", paths: ["*"], time: 2 }
23+
{ kind: "modify-any", paths: ["*"], time: 3 }
24+
]
25+
}
26+
/watch/file-2: {
27+
events: [
28+
{ kind: "rename-to", paths: ["*"], time: 4 }
29+
{ kind: "modify-any", paths: ["*"], time: 1 }
30+
]
31+
}
32+
}
33+
events: {
34+
long: [
35+
{ kind: "create-any", paths: ["/watch/file-1"], time: 2 }
36+
{ kind: "modify-any", paths: ["/watch/file-1"], time: 3 }
37+
{ kind: "rename-to", paths: ["/watch/file-2"], time: 4 }
38+
{ kind: "modify-any", paths: ["/watch/file-2"], time: 1 }
39+
]
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)