use std::sync::mpsc::{sync_channel, Receiver};
use std::thread::JoinHandle;
pub fn try_prefetch<T, E, F>(producer: F, capacity: usize) -> Prefetch<Result<T, E>>
where
T: Send + 'static,
E: Send + 'static,
F: FnOnce(&mut dyn FnMut(T) -> bool) -> Result<(), E> + Send + 'static,
{
let (tx, rx) = sync_channel(capacity);
let handle = std::thread::Builder::new()
.name("koth-reader".into())
.spawn(move || {
if let Err(error) = producer(&mut |item| tx.send(Ok(item)).is_ok()) {
let _ = tx.send(Err(error));
}
})
.expect("failed to spawn reader thread");
Prefetch {
rx: Some(rx),
handle: Some(handle),
}
}
pub fn prefetch<I>(source: I, capacity: usize) -> Prefetch<I::Item>
where
I: Iterator + Send + 'static,
I::Item: Send + 'static,
{
let (tx, rx) = sync_channel(capacity);
let handle = std::thread::Builder::new()
.name("koth-prefetch".into())
.spawn(move || {
for item in source {
if tx.send(item).is_err() {
break;
}
}
})
.expect("failed to spawn prefetch thread");
Prefetch {
rx: Some(rx),
handle: Some(handle),
}
}
pub struct Prefetch<T> {
rx: Option<Receiver<T>>,
handle: Option<JoinHandle<()>>,
}
impl<T> Prefetch<T> {
fn finish(&mut self) {
self.rx = None;
if let Some(h) = self.handle.take() {
if let Err(panic) = h.join() {
std::panic::resume_unwind(panic);
}
}
}
}
impl<T> Iterator for Prefetch<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self.rx.as_ref().and_then(|rx| rx.recv().ok()) {
Some(item) => Some(item),
None => {
self.finish();
None
}
}
}
}
impl<T> Drop for Prefetch<T> {
fn drop(&mut self) {
self.rx = None;
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::time::Duration;
#[test]
fn fallible_producer_is_bounded_and_cancels_on_drop() {
let count = Arc::new(AtomicUsize::new(0));
let produced = count.clone();
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
let stream = try_prefetch::<usize, (), _>(
move |emit| {
for i in 0..1000 {
produced.fetch_add(1, Ordering::SeqCst);
if i == 4 {
ready_tx.send(()).unwrap();
}
if !emit(i) {
break;
}
}
Ok(())
},
4,
);
ready_rx.recv_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(count.load(Ordering::SeqCst), 5);
drop(stream); assert_eq!(count.load(Ordering::SeqCst), 5);
}
#[test]
fn fallible_producer_delivers_ordered_values_then_error() {
let mut stream = try_prefetch(
|emit| {
assert!(emit(1));
assert!(emit(2));
Err("decode failure")
},
1,
);
assert_eq!(stream.next(), Some(Ok(1)));
assert_eq!(stream.next(), Some(Ok(2)));
assert_eq!(stream.next(), Some(Err("decode failure")));
assert_eq!(stream.next(), None);
assert_eq!(stream.next(), None);
}
#[test]
fn producer_panic_reaches_consumer() {
let mut stream = try_prefetch::<(), (), _>(|_| panic!("reader panic"), 1);
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| stream.next())).is_err());
}
}