weavatrix_scan/
parallel.rs1use crate::pool::ThreadPool;
2use crate::walker::{ErrorPolicy, WalkEntry, WalkError, WalkOptions};
3use std::path::PathBuf;
4use std::sync::{Arc, mpsc};
5
6mod collect;
7mod visit;
8mod visit_worker;
9
10use collect::{DirectoryTask, collect_lane, collect_serial, collect_shallow};
11pub use visit::{ParallelVisitReport, WalkControl, WalkEvent};
12
13#[derive(Debug)]
15pub struct ParallelWalkReport {
16 pub entries: Vec<WalkEntry>,
17 pub errors: Vec<WalkError>,
18}
19
20pub struct ParallelWalker {
30 pub(super) root: PathBuf,
31 pub(super) options: WalkOptions,
32 pub(super) parallelism: usize,
33}
34
35impl ParallelWalker {
36 #[must_use]
37 pub fn new(root: impl Into<PathBuf>) -> Self {
38 Self {
39 root: root.into(),
40 options: WalkOptions::default(),
41 parallelism: 0,
42 }
43 }
44
45 #[must_use]
46 pub const fn options(mut self, options: WalkOptions) -> Self {
47 self.options = options;
48 self
49 }
50
51 #[must_use]
53 pub const fn with_parallelism(mut self, parallelism: usize) -> Self {
54 self.parallelism = parallelism;
55 self
56 }
57
58 pub fn walk(mut self) -> Result<ParallelWalkReport, WalkError> {
69 self.options = self.options.normalized();
70 if self.options.follow_links {
71 return collect_serial(&self.root, self.options);
72 }
73 let shallow = collect_shallow(&self.root, self.options)?;
74 let mut entries = shallow.entries;
75 let mut errors = shallow.errors;
76 let tasks = shallow.tasks;
77 let parallel_root = shallow.root;
78 if self.options.error_policy == ErrorPolicy::Abort && !errors.is_empty() {
79 return Err(errors.remove(0));
80 }
81 if tasks.is_empty() {
82 return Ok(ParallelWalkReport { entries, errors });
83 }
84
85 let pool = ThreadPool::global();
86 let worker_count =
87 parallel_worker_count(self.parallelism, self.options.max_open, tasks.len());
88 let task_count = tasks.len();
89 let mut lanes = (0..worker_count)
90 .map(|_| Vec::<DirectoryTask>::new())
91 .collect::<Vec<_>>();
92 for (index, task) in tasks.into_iter().enumerate() {
93 lanes[index % worker_count].push(task);
94 }
95 let (sender, receiver) = mpsc::channel();
96 for (index, lane) in lanes.into_iter().enumerate() {
97 let sender = sender.clone();
98 let root = Arc::clone(¶llel_root);
99 let options = self.options;
100 pool.execute(move || {
101 let report = collect_lane(lane, options, &root);
102 let _ = sender.send((index, report));
103 });
104 }
105 drop(sender);
106 let mut completed = (0..worker_count).map(|_| None).collect::<Vec<_>>();
107 for (index, report) in receiver {
108 completed[index] = Some(report);
109 }
110 let (additional_entries, additional_errors) =
111 completed
112 .iter()
113 .flatten()
114 .fold((0, 0), |(entries, errors), lane| {
115 (
116 entries + lane.report.entries.len(),
117 errors + lane.report.errors.len(),
118 )
119 });
120 entries.reserve(additional_entries);
121 errors.reserve(additional_errors);
122 let mut lanes = completed
123 .into_iter()
124 .map(|lane| {
125 let lane = lane.expect("every parallel lane reports completion");
126 (
127 lane.report.entries.into_iter(),
128 lane.report.errors.into_iter(),
129 lane.segments,
130 )
131 })
132 .collect::<Vec<_>>();
133 for task_index in 0..task_count {
134 let (lane_entries, lane_errors, segments) = &mut lanes[task_index % worker_count];
135 let segment = segments
136 .pop_front()
137 .expect("every directory task has an output segment");
138 entries.extend(lane_entries.by_ref().take(segment.entries));
139 errors.extend(lane_errors.by_ref().take(segment.errors));
140 }
141 if self.options.error_policy == ErrorPolicy::Abort && !errors.is_empty() {
142 return Err(errors.remove(0));
143 }
144 Ok(ParallelWalkReport { entries, errors })
145 }
146}
147
148pub(super) fn parallel_worker_count(parallelism: usize, max_open: usize, tasks: usize) -> usize {
149 let available = ThreadPool::global().workers();
150 let requested = if parallelism == 0 {
151 available
152 } else {
153 parallelism
154 };
155 requested.min(max_open.max(1)).min(tasks).max(1)
156}