use super::{ParallelMultiWalker, root_worker_count};
use crate::control::CancellationToken;
use crate::parallel::{ParallelVisitReport, ParallelWalker, WalkControl, WalkEvent};
use crate::runtime::ParallelRuntime;
use crate::walk_types::WalkError;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug)]
pub struct ParallelMultiWalkEvent<'a> {
pub root_index: usize,
pub root: &'a Path,
pub event: WalkEvent<'a>,
}
#[derive(Debug)]
pub struct ParallelMultiVisitReport {
pub reports: Vec<ParallelVisitReport>,
}
impl ParallelMultiVisitReport {
#[must_use]
pub const fn len(&self) -> usize {
self.reports.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.reports.is_empty()
}
#[must_use]
pub fn visited(&self) -> u64 {
self.reports
.iter()
.fold(0_u64, |total, report| total.saturating_add(report.visited))
}
#[must_use]
pub fn quit(&self) -> bool {
self.reports.iter().any(|report| report.quit)
}
#[must_use]
pub fn cancelled(&self) -> bool {
self.reports.iter().any(|report| report.cancelled)
}
}
impl ParallelMultiWalker {
pub fn visit<F>(self, visitor: F) -> Result<ParallelMultiVisitReport, WalkError>
where
F: for<'entry> Fn(ParallelMultiWalkEvent<'entry>) -> WalkControl + Send + Sync + 'static,
{
self.visit_with_cancellation(&CancellationToken::new(), visitor)
}
pub fn visit_with_cancellation<F>(
self,
cancellation: &CancellationToken,
visitor: F,
) -> Result<ParallelMultiVisitReport, WalkError>
where
F: for<'entry> Fn(ParallelMultiWalkEvent<'entry>) -> WalkControl + Send + Sync + 'static,
{
let worker_count = if self.runtime.is_worker_thread() {
1
} else {
root_worker_count(self.root_parallelism, self.roots.len())
};
let visitor = Arc::new(visitor);
if worker_count <= 1 {
let reports = self
.roots
.into_iter()
.enumerate()
.map(|(root_index, root)| {
visit_root(
root_index,
root,
self.options,
self.traversal_parallelism,
self.skip_stdout,
self.runtime.clone(),
cancellation,
Arc::clone(&visitor),
)
})
.collect::<Result<Vec<_>, _>>()?;
return Ok(ParallelMultiVisitReport { reports });
}
let chunk_size = self.roots.len().div_ceil(worker_count);
let indexed = self.roots.into_iter().enumerate().collect::<Vec<_>>();
let mut visited = std::thread::scope(|scope| {
indexed
.chunks(chunk_size)
.map(|chunk| {
let visitor = Arc::clone(&visitor);
let runtime = self.runtime.clone();
let cancellation = cancellation.clone();
scope.spawn(move || {
chunk
.iter()
.map(|(root_index, root)| {
(
*root_index,
visit_root(
*root_index,
root.clone(),
self.options,
self.traversal_parallelism,
self.skip_stdout,
runtime.clone(),
&cancellation,
Arc::clone(&visitor),
),
)
})
.collect::<Vec<_>>()
})
})
.collect::<Vec<_>>()
.into_iter()
.flat_map(|handle| handle.join().expect("multi-root streaming worker panicked"))
.collect::<Vec<_>>()
});
visited.sort_unstable_by_key(|(index, _)| *index);
let reports = visited
.into_iter()
.map(|(_, report)| report)
.collect::<Result<Vec<_>, _>>()?;
Ok(ParallelMultiVisitReport { reports })
}
}
#[allow(clippy::too_many_arguments)]
fn visit_root<F>(
root_index: usize,
root: std::path::PathBuf,
options: crate::WalkOptions,
traversal_parallelism: usize,
skip_stdout: bool,
runtime: ParallelRuntime,
cancellation: &CancellationToken,
visitor: Arc<F>,
) -> Result<ParallelVisitReport, WalkError>
where
F: for<'entry> Fn(ParallelMultiWalkEvent<'entry>) -> WalkControl + Send + Sync + 'static,
{
let event_root = root.clone();
let quit_cancellation = cancellation.clone();
let result = ParallelWalker::new(root)
.options(options)
.with_parallelism(traversal_parallelism)
.runtime(runtime)
.skip_stdout(skip_stdout)
.visit_with_cancellation(cancellation, move |event| {
let control = visitor(ParallelMultiWalkEvent {
root_index,
root: &event_root,
event,
});
if control == WalkControl::Quit {
quit_cancellation.cancel();
}
control
});
if result.is_err() {
cancellation.cancel();
}
result
}