Skip to main content

gix_pack/index/traverse/
with_lookup.rs

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
15/// Traversal options for [`index::File::traverse_with_lookup()`]
16pub struct Options<F> {
17    /// If `Some`, only use the given number of threads. Otherwise, the number of threads to use will be selected based on
18    /// the number of available logical cores.
19    pub thread_limit: Option<usize>,
20    /// The kinds of safety checks to perform.
21    pub check: index::traverse::SafetyCheck,
22    /// A function to create a pack cache
23    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/// The progress ids used in [`index::File::traverse_with_lookup()`].
37///
38/// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
39#[derive(Debug, Copy, Clone)]
40pub enum ProgressId {
41    /// The amount of bytes currently processed to generate a checksum of the *pack data file*.
42    HashPackDataBytes,
43    /// The amount of bytes currently processed to generate a checksum of the *pack index file*.
44    HashPackIndexBytes,
45    /// Collect all object hashes into a vector and sort it by their pack offset.
46    CollectSortedIndexEntries,
47    /// The amount of objects which were decoded by brute-force.
48    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
62/// Verify and validate the content of the index file
63impl<T> index::File<T>
64where
65    T: crate::FileData + Sync,
66{
67    /// Iterate through all _decoded objects_ in the given `pack` and handle them with a `Processor` using a cache to reduce the amount of
68    /// waste while decoding objects.
69    ///
70    /// For more details, see the documentation on the [`traverse()`][index::File::traverse()] method.
71    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), // decode buffer
133                            gix_zlib::Inflate::default(),
134                            lock(&reduce_progress)
135                                .add_child_with_id(format!("thread {index}"), gix_features::progress::UNKNOWN), // per thread progress
136                        )
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}