weavatrix_scan/parallel_multi/
mod.rs1use crate::parallel::{ParallelWalkReport, ParallelWalker};
2use crate::runtime::ParallelRuntime;
3use crate::walk_types::{WalkError, WalkOptions};
4use std::path::PathBuf;
5
6mod visit;
7
8pub use visit::{ParallelMultiVisitReport, ParallelMultiWalkEvent};
9
10#[derive(Debug)]
12pub struct ParallelMultiWalkReport {
13 pub reports: Vec<ParallelWalkReport>,
14}
15
16impl ParallelMultiWalkReport {
17 #[must_use]
18 pub const fn len(&self) -> usize {
19 self.reports.len()
20 }
21
22 #[must_use]
23 pub const fn is_empty(&self) -> bool {
24 self.reports.is_empty()
25 }
26}
27
28pub struct ParallelMultiWalker {
30 roots: Vec<PathBuf>,
31 options: WalkOptions,
32 root_parallelism: usize,
33 traversal_parallelism: usize,
34 skip_stdout: bool,
35 runtime: ParallelRuntime,
36}
37
38impl ParallelMultiWalker {
39 #[must_use]
40 pub fn new(root: impl Into<PathBuf>) -> Self {
41 Self {
42 roots: vec![root.into()],
43 options: WalkOptions::default(),
44 root_parallelism: 0,
45 traversal_parallelism: 0,
46 skip_stdout: false,
47 runtime: ParallelRuntime::global(),
48 }
49 }
50
51 #[must_use]
52 pub fn add_root(mut self, root: impl Into<PathBuf>) -> Self {
53 self.roots.push(root.into());
54 self
55 }
56
57 #[must_use]
58 pub const fn options(mut self, options: WalkOptions) -> Self {
59 self.options = options;
60 self
61 }
62
63 #[must_use]
65 pub const fn with_root_parallelism(mut self, parallelism: usize) -> Self {
66 self.root_parallelism = parallelism;
67 self
68 }
69
70 #[must_use]
72 pub const fn with_traversal_parallelism(mut self, parallelism: usize) -> Self {
73 self.traversal_parallelism = parallelism;
74 self
75 }
76
77 #[must_use]
79 pub fn runtime(mut self, runtime: ParallelRuntime) -> Self {
80 self.runtime = runtime;
81 self
82 }
83
84 #[must_use]
86 pub const fn skip_stdout(mut self, enabled: bool) -> Self {
87 self.skip_stdout = enabled;
88 self
89 }
90
91 pub fn walk(self) -> Result<ParallelMultiWalkReport, WalkError> {
102 let worker_count = if self.runtime.is_worker_thread() {
103 1
104 } else {
105 root_worker_count(self.root_parallelism, self.roots.len())
106 };
107 if worker_count <= 1 {
108 let reports = self
109 .roots
110 .into_iter()
111 .map(|root| {
112 ParallelWalker::new(root)
113 .options(self.options)
114 .with_parallelism(self.traversal_parallelism)
115 .runtime(self.runtime.clone())
116 .skip_stdout(self.skip_stdout)
117 .walk()
118 })
119 .collect::<Result<Vec<_>, _>>()?;
120 return Ok(ParallelMultiWalkReport { reports });
121 }
122
123 let chunk_size = self.roots.len().div_ceil(worker_count);
124 let indexed = self.roots.into_iter().enumerate().collect::<Vec<_>>();
125 let mut walked = std::thread::scope(|scope| {
126 indexed
127 .chunks(chunk_size)
128 .map(|chunk| {
129 scope.spawn(|| {
130 chunk
131 .iter()
132 .map(|(index, root)| {
133 (
134 *index,
135 ParallelWalker::new(root)
136 .options(self.options)
137 .with_parallelism(self.traversal_parallelism)
138 .runtime(self.runtime.clone())
139 .skip_stdout(self.skip_stdout)
140 .walk(),
141 )
142 })
143 .collect::<Vec<_>>()
144 })
145 })
146 .collect::<Vec<_>>()
147 .into_iter()
148 .flat_map(|handle| handle.join().expect("multi-root walk worker panicked"))
149 .collect::<Vec<_>>()
150 });
151 walked.sort_unstable_by_key(|(index, _)| *index);
152 let reports = walked
153 .into_iter()
154 .map(|(_, report)| report)
155 .collect::<Result<Vec<_>, _>>()?;
156 Ok(ParallelMultiWalkReport { reports })
157 }
158}
159
160fn root_worker_count(requested: usize, roots: usize) -> usize {
161 if roots == 0 {
162 return 1;
163 }
164 let available = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
165 let requested = if requested == 0 {
166 available.min(if cfg!(windows) { 8 } else { 16 })
167 } else {
168 requested.min(available)
169 };
170 requested.min(roots).max(1)
171}