use crate::types::DocumentId;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProgressEvent {
FileStarted {
index: usize,
total: usize,
corpus: String,
document: DocumentId,
},
FileChunked {
chunks: usize,
},
FileEmbedded {
chunks: usize,
},
FileStored,
ChunksEmbedded {
done: usize,
total: usize,
},
FileFailed {
error: String,
},
}
pub trait Progress: Send + Sync {
fn on_event(&self, event: &ProgressEvent);
}
impl<F> Progress for F
where
F: Fn(&ProgressEvent) + Send + Sync,
{
fn on_event(&self, event: &ProgressEvent) {
self(event);
}
}
pub trait Cancel: Send + Sync {
fn is_cancelled(&self) -> bool;
}
#[derive(Clone, Debug, Default)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Relaxed);
}
}
impl Cancel for CancellationToken {
fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Relaxed)
}
}
impl Cancel for Arc<AtomicBool> {
fn is_cancelled(&self) -> bool {
self.load(Ordering::Relaxed)
}
}
impl<F> Cancel for F
where
F: Fn() -> bool + Send + Sync,
{
fn is_cancelled(&self) -> bool {
self()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
#[derive(Default)]
struct RecordingProgress {
events: Mutex<Vec<ProgressEvent>>,
}
impl Progress for RecordingProgress {
fn on_event(&self, event: &ProgressEvent) {
self.events.lock().unwrap().push(event.clone());
}
}
#[test]
fn cancellation_token_starts_uncancelled() {
let token = CancellationToken::new();
assert!(!token.is_cancelled());
}
#[test]
fn cancellation_token_default_is_uncancelled() {
let token = CancellationToken::default();
assert!(!token.is_cancelled());
}
#[test]
fn cancellation_token_flips_and_is_shared_across_clones() {
let token = CancellationToken::new();
let clone = token.clone();
token.cancel();
assert!(token.is_cancelled());
assert!(clone.is_cancelled());
clone.cancel();
assert!(token.is_cancelled());
}
#[test]
fn cancellation_token_works_through_trait_object() {
let token = CancellationToken::new();
let dyn_cancel: &dyn Cancel = &token;
assert!(!dyn_cancel.is_cancelled());
token.cancel();
assert!(dyn_cancel.is_cancelled());
}
#[test]
fn arc_atomic_bool_is_a_cancel_source() {
let flag = Arc::new(AtomicBool::new(false));
let dyn_cancel: &dyn Cancel = &flag;
assert!(!dyn_cancel.is_cancelled());
flag.store(true, Ordering::Relaxed);
assert!(dyn_cancel.is_cancelled());
}
#[test]
fn closure_is_a_cancel_source() {
let flag = Arc::new(AtomicBool::new(false));
let captured = flag.clone();
let closure = move || captured.load(Ordering::Relaxed);
let dyn_cancel: &dyn Cancel = &closure;
assert!(!dyn_cancel.is_cancelled());
flag.store(true, Ordering::Relaxed);
assert!(dyn_cancel.is_cancelled());
}
#[test]
fn closure_is_a_progress_sink() {
let seen = Arc::new(Mutex::new(Vec::<ProgressEvent>::new()));
let captured = seen.clone();
let sink = move |event: &ProgressEvent| captured.lock().unwrap().push(event.clone());
let dyn_progress: &dyn Progress = &sink;
let event = ProgressEvent::FileStored;
dyn_progress.on_event(&event);
let seen = seen.lock().unwrap();
assert_eq!(seen.len(), 1);
assert_eq!(seen[0], ProgressEvent::FileStored);
}
#[test]
fn named_progress_impl_receives_events_in_order() {
let recorder = RecordingProgress::default();
let events = [
ProgressEvent::FileStarted {
index: 0,
total: 2,
corpus: "papers".to_string(),
document: DocumentId("a.pdf".to_string()),
},
ProgressEvent::FileChunked { chunks: 12 },
ProgressEvent::FileEmbedded { chunks: 12 },
ProgressEvent::FileStored,
ProgressEvent::FileFailed {
error: "parse error".to_string(),
},
];
for event in &events {
recorder.on_event(event);
}
let recorded = recorder.events.lock().unwrap();
assert_eq!(&*recorded, &events[..]);
}
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn traits_are_object_safe_and_thread_safe() {
assert_send_sync::<Box<dyn Progress>>();
assert_send_sync::<Box<dyn Cancel>>();
assert_send_sync::<CancellationToken>();
fn observe(p: &dyn Progress, c: &dyn Cancel) -> bool {
p.on_event(&ProgressEvent::FileStored);
c.is_cancelled()
}
let recorder = RecordingProgress::default();
let token = CancellationToken::new();
assert!(!observe(&recorder, &token));
token.cancel();
assert!(observe(&recorder, &token));
assert_eq!(
recorder.events.lock().unwrap().as_slice(),
&[ProgressEvent::FileStored, ProgressEvent::FileStored]
);
}
}