use std::any::Any;
use std::collections::{HashMap, VecDeque};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::sync::{Mutex, mpsc};
use super::{RetentionError, Visit};
use crate::ObjectId;
pub(super) type Visitor<'a> = dyn Fn(ObjectId) -> Result<Visit, RetentionError> + Sync + 'a;
const AHEAD_PER_THREAD: usize = 64;
type Outcome = Result<Result<Visit, RetentionError>, Box<dyn Any + Send>>;
pub(super) struct Reader<'a> {
visit: &'a Visitor<'a>,
queue: VecDeque<(u64, ObjectId, bool)>,
next_seq: u64,
workers: Option<Workers>,
}
struct Workers {
tasks: mpsc::Sender<(u64, ObjectId)>,
results: mpsc::Receiver<(u64, Outcome)>,
ready: HashMap<u64, Outcome>,
dispatched_to: u64,
in_flight: usize,
capacity: usize,
}
pub(super) fn with_reader<T>(
max_threads: usize,
visit: &Visitor<'_>,
body: impl FnOnce(&mut Reader<'_>) -> Result<T, RetentionError>,
) -> Result<T, RetentionError> {
let threads = std::thread::available_parallelism()
.map_or(1, std::num::NonZeroUsize::get)
.min(max_threads);
if threads < 2 {
return body(&mut Reader {
visit,
queue: VecDeque::new(),
next_seq: 0,
workers: None,
});
}
let (tasks, queue) = mpsc::channel::<(u64, ObjectId)>();
let (done, results) = mpsc::channel();
let queue = Mutex::new(queue);
std::thread::scope(|scope| {
for _ in 0..threads {
let done = done.clone();
let queue = &queue;
scope.spawn(move || {
loop {
let task = queue.lock().unwrap_or_else(|e| e.into_inner()).recv();
let Ok((seq, id)) = task else {
return;
};
let outcome = catch_unwind(AssertUnwindSafe(|| visit(id)));
if done.send((seq, outcome)).is_err() {
return;
}
}
});
}
drop(done);
let mut reader = Reader {
visit,
queue: VecDeque::new(),
next_seq: 0,
workers: Some(Workers {
tasks,
results,
ready: HashMap::new(),
dispatched_to: 0,
in_flight: 0,
capacity: threads * AHEAD_PER_THREAD,
}),
};
body(&mut reader)
})
}
impl Reader<'_> {
pub(super) fn push(&mut self, id: ObjectId, read: bool) {
self.queue.push_back((self.next_seq, id, read));
self.next_seq += 1;
}
pub(super) fn next(&mut self) -> Option<(ObjectId, Option<Result<Visit, RetentionError>>)> {
let Some(workers) = &mut self.workers else {
let (_, id, read) = self.queue.pop_front()?;
return Some((id, read.then(|| (self.visit)(id))));
};
workers.dispatch(&self.queue);
let (seq, id, read) = self.queue.pop_front()?;
if !read {
return Some((id, None));
}
workers.in_flight -= 1;
let outcome = match workers.ready.remove(&seq) {
Some(outcome) => outcome,
None => loop {
let (arrived, outcome) = workers
.results
.recv()
.expect("walk workers outlive their reader");
if arrived == seq {
break outcome;
}
workers.ready.insert(arrived, outcome);
},
};
Some((
id,
Some(outcome.unwrap_or_else(|panic| resume_unwind(panic))),
))
}
}
impl Workers {
fn dispatch(&mut self, queue: &VecDeque<(u64, ObjectId, bool)>) {
let Some(&(first, _, _)) = queue.front() else {
return;
};
let start = usize::try_from(self.dispatched_to.saturating_sub(first))
.map_or(queue.len(), |start| start.min(queue.len()));
for &(seq, id, read) in queue.range(start..) {
if self.in_flight == self.capacity {
return;
}
if read {
if self.tasks.send((seq, id)).is_err() {
return;
}
self.in_flight += 1;
}
self.dispatched_to = seq + 1;
}
}
}