From 8a517d932cd4e3332790667d215de5ab191a22a8 Mon Sep 17 00:00:00 2001 From: pantonshire Date: Sat, 19 Aug 2023 09:04:34 +0100 Subject: [PATCH] gracefully shut down the channel on drop instead of discarding events Previously, if the debouncer's shutdown flag was set while there was anything in its accumulator, the contents of the accumulator would never be sent through the mpsc channel, effectively causing the events folded into the accumulator to be lost. This patch modifies the shutdown behaviour to send the contents of the accumulator through the channel before stopping the debouncer and closing the channel. --- src/lib.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dd7ec69..cfae023 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,7 +301,15 @@ fn debounce_thread( }; match event { - DebouncerEvent::Shutdown => break 'debounce, + DebouncerEvent::Shutdown => { + // Before shutting down, check if there is anything in the accumulator and send it + // through the channel if it is still open. + if let Some(acc) = guard.swap_acc() { + tx.send(acc).ok(); + } + + break 'debounce; + }, DebouncerEvent::Dirty => { // Wait for the debounce time, or until the `sleep_cvar` is notified and the @@ -315,16 +323,68 @@ fn debounce_thread( !state.should_shutdown() }).unwrap(); - if guard.should_shutdown() { - break 'debounce; - } - + // Once we have finished waiting, send the contents of the accumulator through the + // channel. if let Some(acc) = guard.swap_acc() { if tx.send(acc).is_err() { - break 'debounce + // If the other side of the channel has been closed, stop the debouncer. + break 'debounce; } } + + // Check if the shutdown flag was set while we were waiting, and stop the deboucner + // if so. + if guard.should_shutdown() { + break 'debounce; + } }, } } } + +#[cfg(test)] +mod tests { + use std::{time::Duration, thread}; + + use super::{Debouncer, fold}; + + #[test] + fn test_debounce() { + let (debouncer, rx) = Debouncer::new( + Duration::from_millis(50), + fold::fold_vec_push:: + ).unwrap(); + + for i in 0..3 { + for j in 0..10 { + debouncer.debounce(i * 10 + j); + thread::sleep(Duration::from_millis(4)); + } + + thread::sleep(Duration::from_millis(20)); + } + + assert_eq!(rx.recv().unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert_eq!(rx.recv().unwrap(), &[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]); + assert_eq!(rx.recv().unwrap(), &[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]); + } + + #[test] + fn test_debouncer_shutdown() { + let (debouncer, rx) = Debouncer::new( + Duration::from_millis(100), + fold::fold_vec_push:: + ).unwrap(); + + debouncer.debounce(1); + debouncer.debounce(2); + debouncer.debounce(3); + + // Drop the debouncer, shutting it down. + drop(debouncer); + + // Test that the events emitted just before the shutdown are not lost. + assert_eq!(rx.recv().unwrap(), &[1, 2, 3]); + assert!(rx.recv().is_err()); + } +}