1use std::sync::atomic::{AtomicBool, Ordering};
2
3use gix_features::{
4 parallel::{self, in_parallel_if},
5 progress::{self, Count, DynNestedProgress, Progress},
6 threading::{Mutable, OwnShared, lock},
7};
8
9use super::{Error, Reducer};
10use crate::{
11 data, exact_vec, index,
12 index::{traverse::Outcome, util},
13};
14
15pub struct Options<F> {
17 pub thread_limit: Option<usize>,
20 pub check: index::traverse::SafetyCheck,
22 pub make_pack_lookup_cache: F,
24}
25
26impl Default for Options<fn() -> crate::cache::Never> {
27 fn default() -> Self {
28 Options {
29 check: Default::default(),
30 thread_limit: None,
31 make_pack_lookup_cache: || crate::cache::Never,
32 }
33 }
34}
35
36#[derive(Debug, Copy, Clone)]
40pub enum ProgressId {
41 HashPackDataBytes,
43 HashPackIndexBytes,
45 CollectSortedIndexEntries,
47 DecodedObjects,
49}
50
51impl From<ProgressId> for gix_features::progress::Id {
52 fn from(v: ProgressId) -> Self {
53 match v {
54 ProgressId::HashPackDataBytes => *b"PTHP",
55 ProgressId::HashPackIndexBytes => *b"PTHI",
56 ProgressId::CollectSortedIndexEntries => *b"PTCE",
57 ProgressId::DecodedObjects => *b"PTRO",
58 }
59 }
60}
61
62impl<T> index::File<T>
64where
65 T: crate::FileData + Sync,
66{
67 pub fn traverse_with_lookup<C, Processor, E, F, D>(
72 &self,
73 mut processor: Processor,
74 pack: &data::File<D>,
75 progress: &mut dyn DynNestedProgress,
76 should_interrupt: &AtomicBool,
77 Options {
78 thread_limit,
79 check,
80 make_pack_lookup_cache,
81 }: Options<F>,
82 ) -> Result<Outcome, Error<E>>
83 where
84 C: crate::cache::DecodeEntry,
85 E: std::error::Error + Send + Sync + 'static,
86 Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn Progress) -> Result<(), E> + Send + Clone,
87 F: Fn() -> C + Send + Clone,
88 D: crate::FileData + Send + Sync,
89 {
90 let (verify_result, traversal_result) = parallel::join(
91 {
92 let mut pack_progress = progress.add_child_with_id(
93 format!("Hash of pack '{}'", crate::source_name(pack.path())),
94 ProgressId::HashPackDataBytes.into(),
95 );
96 let mut index_progress = progress.add_child_with_id(
97 format!("Hash of index '{}'", crate::source_name(&self.path)),
98 ProgressId::HashPackIndexBytes.into(),
99 );
100 move || {
101 let res =
102 self.possibly_verify(pack, check, &mut pack_progress, &mut index_progress, should_interrupt);
103 if res.is_err() {
104 should_interrupt.store(true, Ordering::SeqCst);
105 }
106 res
107 }
108 },
109 || {
110 let index_entries = util::index_entries_sorted_by_offset_ascending(
111 self,
112 &mut progress.add_child_with_id(
113 "collecting sorted index".into(),
114 ProgressId::CollectSortedIndexEntries.into(),
115 ),
116 );
117
118 let (chunk_size, thread_limit, available_cores) =
119 parallel::optimize_chunk_size_and_thread_limit(1000, Some(index_entries.len()), thread_limit, None);
120 let there_are_enough_entries_to_process = || index_entries.len() > chunk_size * available_cores;
121 let input_chunks = index_entries.chunks(chunk_size);
122 let reduce_progress = OwnShared::new(Mutable::new({
123 let mut p = progress.add_child_with_id("Traversing".into(), ProgressId::DecodedObjects.into());
124 p.init(Some(self.num_objects() as usize), progress::count("objects"));
125 p
126 }));
127 let state_per_thread = {
128 let reduce_progress = reduce_progress.clone();
129 move |index| {
130 (
131 make_pack_lookup_cache(),
132 Vec::with_capacity(2048), gix_zlib::Inflate::default(),
134 lock(&reduce_progress)
135 .add_child_with_id(format!("thread {index}"), gix_features::progress::UNKNOWN), )
137 }
138 };
139
140 in_parallel_if(
141 there_are_enough_entries_to_process,
142 input_chunks,
143 thread_limit,
144 state_per_thread,
145 move |entries: &[index::Entry],
146 (cache, buf, inflate, progress)|
147 -> Result<Vec<data::decode::entry::Outcome>, Error<_>> {
148 progress.init(
149 Some(entries.len()),
150 gix_features::progress::count_with_decimals("objects", 2),
151 );
152 let mut stats = exact_vec(entries.len());
153 progress.set(0);
154 for index_entry in entries.iter() {
155 let result = self.decode_and_process_entry(
156 check,
157 pack,
158 cache,
159 buf,
160 inflate,
161 progress,
162 index_entry,
163 &mut processor,
164 );
165 progress.inc();
166 let stat = match result {
167 Err(err @ Error::PackDecode { .. }) if !check.fatal_decode_error() => {
168 progress.info(format!("Ignoring decode error: {err}"));
169 continue;
170 }
171 res => res,
172 }?;
173 stats.push(stat);
174 if should_interrupt.load(Ordering::Relaxed) {
175 break;
176 }
177 }
178 Ok(stats)
179 },
180 Reducer::from_progress(reduce_progress, pack.data_len(), check, should_interrupt),
181 )
182 },
183 );
184 Ok(Outcome {
185 actual_index_checksum: verify_result?,
186 statistics: traversal_result?,
187 })
188 }
189}