History

feat!: index-backed tree traversal with a form of work-stealing. (#851)

When delta-trees are unbalanced, in pathological cases it's possible that that one thread ends up with more than half of the work. In this case it's required that it manages to spawn its own threads to parallelize the work it has.

Sebastian Thiel committed 3 years ago

Commit d22dd8f1 parent add5ea811 files · +441 −202Browse files

Unified · Split

11 files
  1. gix-pack/Cargo.toml +1 −1
  2. gix-pack/src/bundle/write/mod.rs +13 −16
  3. gix-pack/src/cache/delta/traverse/mod.rs +34 −26
  4. gix-pack/src/cache/delta/traverse/resolve.rs +314 −51
  5. gix-pack/src/cache/delta/traverse/util.rs +7 −5
  6. gix-pack/src/index/traverse/mod.rs +18 −22
  7. gix-pack/src/index/traverse/with_index.rs +16 −20
  8. gix-pack/src/index/traverse/with_lookup.rs +8 −12
  9. gix-pack/src/index/verify.rs +5 −8
  10. gix-pack/src/index/write/mod.rs +9 −14
  11. gix-pack/tests/pack/index.rs +16 −27
gix-pack/Cargo.toml+1−1
@@ -5,7 +5,7 @@ repository = "https://github.com/Byron/gitoxide" · 4 unmodified lines
5authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]5authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
6license = "MIT/Apache-2.0"6license = "MIT/Apache-2.0"
7description = "Implements git packs and related data structures"7description = "Implements git packs and related data structures"
8edition = "2018"8edition = "2021"
9include = ["src/**/*", "CHANGELOG.md"]9include = ["src/**/*", "CHANGELOG.md"]
10rust-version = "1.64"10rust-version = "1.64"
11autotests = false11autotests = false
gix-pack/src/bundle/write/mod.rs+13−16
@@ -62,17 +62,14 @@ impl crate::Bundle { · 61 unmodified lines
62 /// * the resulting pack may be empty, that is, contains zero objects in some situations. This is a valid reply by a server and should62 /// * the resulting pack may be empty, that is, contains zero objects in some situations. This is a valid reply by a server and should
63 /// be accounted for.63 /// be accounted for.
64 /// - Empty packs always have the same name and not handling this case will result in at most one superfluous pack.64 /// - Empty packs always have the same name and not handling this case will result in at most one superfluous pack.
65 pub fn write_to_directory<P>(65 pub fn write_to_directory(
66 pack: impl io::BufRead,66 pack: impl io::BufRead,
67 directory: Option<impl AsRef<Path>>,67 directory: Option<impl AsRef<Path>>,
68 mut progress: P,68 mut progress: impl Progress,
69 should_interrupt: &AtomicBool,69 should_interrupt: &AtomicBool,
70 thin_pack_base_object_lookup_fn: Option<ThinPackLookupFn>,70 thin_pack_base_object_lookup_fn: Option<ThinPackLookupFn>,
71 options: Options,71 options: Options,
72 ) -> Result<Outcome, Error>72 ) -> Result<Outcome, Error> {
73 where
74 P: Progress,
75 {
76 let mut read_progress = progress.add_child_with_id("read pack", ProgressId::ReadPackBytes.into());73 let mut read_progress = progress.add_child_with_id("read pack", ProgressId::ReadPackBytes.into());
77 read_progress.init(None, progress::bytes());74 read_progress.init(None, progress::bytes());
78 let pack = progress::Read {75 let pack = progress::Read {
@@ -354,20 +351,20 @@ impl crate::Bundle { · 275 unmodified lines
354 }351 }
355}352}
356 353
354fn resolve_entry(range: data::EntryRange, mapped_file: &memmap2::Mmap) -> Option<&[u8]> {
355 mapped_file.get(range.start as usize..range.end as usize)
356}
357
357fn new_pack_file_resolver(358fn new_pack_file_resolver(
358 data_file: SharedTempFile,359 data_file: SharedTempFile,
359) -> io::Result<impl Fn(data::EntryRange, &mut Vec<u8>) -> Option<()> + Send + Clone> {360) -> io::Result<(
361 impl Fn(data::EntryRange, &memmap2::Mmap) -> Option<&[u8]> + Send + Clone,
362 memmap2::Mmap,
363)> {
360 let mut guard = data_file.lock();364 let mut guard = data_file.lock();
361 guard.flush()?;365 guard.flush()?;
362 let mapped_file = Arc::new(crate::mmap::read_only(366 let mapped_file = crate::mmap::read_only(&guard.get_mut().with_mut(|f| f.path().to_owned())?)?;
363 &guard.get_mut().with_mut(|f| f.path().to_owned())?,367 Ok((resolve_entry, mapped_file))
364 )?);
365 let pack_data_lookup = move |range: std::ops::Range<u64>, out: &mut Vec<u8>| -> Option<()> {
366 mapped_file
367 .get(range.start as usize..range.end as usize)
368 .map(|pack_entry| out.copy_from_slice(pack_entry))
369 };
370 Ok(pack_data_lookup)
371}368}
372 369
373struct WriteOutcome {370struct WriteOutcome {
gix-pack/src/cache/delta/traverse/mod.rs+34−26
@@ -1,9 +1,10 @@
1use std::sync::atomic::{AtomicBool, Ordering};1use std::sync::atomic::{AtomicBool, Ordering};
2 2
3use gix_features::threading::{Mutable, OwnShared};
3use gix_features::{4use gix_features::{
4 parallel::in_parallel_with_slice,5 parallel::in_parallel_with_slice,
5 progress::{self, Progress},6 progress::{self, Progress},
6 threading::{lock, Mutable, OwnShared},7 threading,
7};8};
8 9
9use crate::{10use crate::{
@@ -36,18 +37,18 @@ pub enum Error { · 26 unmodified lines
36 /// The base's offset which was from a resolved ref-delta that didn't actually get added to the tree37 /// The base's offset which was from a resolved ref-delta that didn't actually get added to the tree
37 base_pack_offset: crate::data::Offset,38 base_pack_offset: crate::data::Offset,
38 },39 },
40 #[error("Failed to spawn thread when switching to work-stealing mode")]
41 SpawnThread(#[from] std::io::Error),
39}42}
40 43
41/// Additional context passed to the `inspect_object(…)` function of the [`Tree::traverse()`] method.44/// Additional context passed to the `inspect_object(…)` function of the [`Tree::traverse()`] method.
42pub struct Context<'a, S> {45pub struct Context<'a> {
43 /// The pack entry describing the object46 /// The pack entry describing the object
44 pub entry: &'a crate::data::Entry,47 pub entry: &'a crate::data::Entry,
45 /// The offset at which `entry` ends in the pack, useful to learn about the exact range of `entry` within the pack.48 /// The offset at which `entry` ends in the pack, useful to learn about the exact range of `entry` within the pack.
46 pub entry_end: u64,49 pub entry_end: u64,
47 /// The decompressed object itself, ready to be decoded.50 /// The decompressed object itself, ready to be decoded.
48 pub decompressed: &'a [u8],51 pub decompressed: &'a [u8],
49 /// Custom state known to the function
50 pub state: &'a mut S,
51 /// The depth at which this object resides in the delta-tree. It represents the amount of base objects, with 0 indicating52 /// The depth at which this object resides in the delta-tree. It represents the amount of base objects, with 0 indicating
52 /// an 'undeltified' object, and higher values indicating delta objects with the given amount of bases.53 /// an 'undeltified' object, and higher values indicating delta objects with the given amount of bases.
53 pub level: u16,54 pub level: u16,
@@ -89,79 +90,86 @@ where · 35 unmodified lines
89 /// operation as well.90 /// operation as well.
90 /// * `pack_entries_end` marks one-past-the-last byte of the last entry in the pack, as the last entries size would otherwise91 /// * `pack_entries_end` marks one-past-the-last byte of the last entry in the pack, as the last entries size would otherwise
91 /// be unknown as it's not part of the index file.92 /// be unknown as it's not part of the index file.
92 /// * `new_thread_state() -> State` is a function to create state to be used in each thread, invoked once per thread.
93 /// * `inspect_object(node_data: &mut T, progress: Progress, context: Context<ThreadLocal State>) -> Result<(), CustomError>` is a function93 /// * `inspect_object(node_data: &mut T, progress: Progress, context: Context<ThreadLocal State>) -> Result<(), CustomError>` is a function
94 /// running for each thread receiving fully decoded objects along with contextual information, which either succeeds with `Ok(())`94 /// running for each thread receiving fully decoded objects along with contextual information, which either succeeds with `Ok(())`
95 /// or returns a `CustomError`.95 /// or returns a `CustomError`.
96 /// Note that `node_data` can be modified to allow storing maintaining computation results on a per-object basis.96 /// Note that `node_data` can be modified to allow storing maintaining computation results on a per-object basis. It should contain
97 /// its own mutable per-thread data as required.
97 ///98 ///
98 /// This method returns a vector of all tree items, along with their potentially modified custom node data.99 /// This method returns a vector of all tree items, along with their potentially modified custom node data.
99 ///100 ///
100 /// _Note_ that this method consumed the Tree to assure safe parallel traversal with mutation support.101 /// _Note_ that this method consumed the Tree to assure safe parallel traversal with mutation support.
101 pub fn traverse<F, P1, P2, MBFN, S, E>(102 pub fn traverse<F, P1, P2, MBFN, E, R>(
102 mut self,103 mut self,
103 resolve: F,104 resolve: F,
105 resolve_data: &R,
104 pack_entries_end: u64,106 pack_entries_end: u64,
105 new_thread_state: impl Fn() -> S + Send + Clone,
106 inspect_object: MBFN,107 inspect_object: MBFN,
107 Options {108 Options {
108 thread_limit,109 thread_limit,
109 object_progress,110 mut object_progress,
110 mut size_progress,111 mut size_progress,
111 should_interrupt,112 should_interrupt,
112 object_hash,113 object_hash,
113 }: Options<'_, P1, P2>,114 }: Options<'_, P1, P2>,
114 ) -> Result<Outcome<T>, Error>115 ) -> Result<Outcome<T>, Error>
115 where116 where
116 F: for<'r> Fn(EntryRange, &'r mut Vec<u8>) -> Option<()> + Send + Clone,117 F: for<'r> FnMut(EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
118 R: Send + Sync,
117 P1: Progress,119 P1: Progress,
118 P2: Progress,120 P2: Progress,
119 MBFN: Fn(&mut T, &mut <P1 as Progress>::SubProgress, Context<'_, S>) -> Result<(), E> + Send + Clone,121 MBFN: FnMut(&mut T, &<P1 as Progress>::SubProgress, Context<'_>) -> Result<(), E> + Send + Clone,
120 E: std::error::Error + Send + Sync + 'static,122 E: std::error::Error + Send + Sync + 'static,
121 {123 {
122 self.set_pack_entries_end_and_resolve_ref_offsets(pack_entries_end)?;124 self.set_pack_entries_end_and_resolve_ref_offsets(pack_entries_end)?;
123 let object_progress = OwnShared::new(Mutable::new(object_progress));
124 125
125 let num_objects = self.num_items();126 let num_objects = self.num_items();
126 let object_counter = {127 let object_counter = {
127 let mut progress = lock(&object_progress);128 let progress = &mut object_progress;
128 progress.init(Some(num_objects), progress::count("objects"));129 progress.init(Some(num_objects), progress::count("objects"));
129 progress.counter()130 progress.counter()
130 };131 };
131 size_progress.init(None, progress::bytes());132 size_progress.init(None, progress::bytes());
132 let size_counter = size_progress.counter();133 let size_counter = size_progress.counter();
133 let child_items = self.child_items.as_mut_slice();134 let child_items = self.child_items.as_mut_slice();
135 let object_progress = OwnShared::new(Mutable::new(object_progress));
134 136
135 let start = std::time::Instant::now();137 let start = std::time::Instant::now();
136 in_parallel_with_slice(138 in_parallel_with_slice(
137 &mut self.root_items,139 &mut self.root_items,
138 thread_limit,140 thread_limit,
139 {141 {
140 let object_progress = object_progress.clone();
141 let child_items = ItemSliceSend(std::ptr::slice_from_raw_parts_mut(142 let child_items = ItemSliceSend(std::ptr::slice_from_raw_parts_mut(
142 child_items.as_mut_ptr(),143 child_items.as_mut_ptr(),
143 child_items.len(),144 child_items.len(),
144 ));145 ));
145 move |thread_index| {146 {
146 (147 let object_progress = object_progress.clone();
147 Vec::<u8>::with_capacity(4096),148 move |thread_index| {
148 lock(&object_progress)149 let _ = &child_items;
149 .add_child_with_id(format!("thread {thread_index}"), gix_features::progress::UNKNOWN),150 resolve::State {
150 new_thread_state(),151 bytes_buf: Vec::<u8>::with_capacity(4096),
151 resolve.clone(),152 delta_bytes: Vec::<u8>::with_capacity(4096),
152 inspect_object.clone(),153 fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096),
153 child_items.clone(),154 progress: threading::lock(&object_progress)
154 )155 .add_child(format!("gix-pack.delta-traverse.inspect-object.{thread_index}")),
156 resolve: resolve.clone(),
157 modify_base: inspect_object.clone(),
158 child_items: child_items.clone(),
159 }
160 }
155 }161 }
156 },162 },
157 {163 {
158 move |node, state, _threads_left, should_interrupt| {164 move |node, state, threads_left, should_interrupt| {
159 resolve::deltas(165 resolve::deltas(
160 object_counter.clone(),166 object_counter.clone(),
161 size_counter.clone(),167 size_counter.clone(),
162 node,168 node,
163 state,169 state,
170 resolve_data,
164 object_hash.len_in_bytes(),171 object_hash.len_in_bytes(),
172 threads_left,
165 should_interrupt,173 should_interrupt,
166 )174 )
167 }175 }
@@ -170,7 +178,7 @@ where · 2 unmodified lines
170 |_| (),178 |_| (),
171 )?;179 )?;
172 180
173 lock(&object_progress).show_throughput(start);181 threading::lock(&object_progress).show_throughput(start);
174 size_progress.show_throughput(start);182 size_progress.show_throughput(start);
175 183
176 Ok(Outcome {184 Ok(Outcome {
gix-pack/src/cache/delta/traverse/resolve.rs+314−51
@@ -1,7 +1,8 @@
1use std::sync::atomic::AtomicBool;1use std::sync::atomic::{AtomicBool, AtomicIsize};
2use std::{cell::RefCell, collections::BTreeMap, sync::atomic::Ordering};2use std::{collections::BTreeMap, sync::atomic::Ordering};
3 3
4use gix_features::{progress::Progress, zlib};4use gix_features::progress::Progress;
5use gix_features::{threading, zlib};
5 6
6use crate::{7use crate::{
7 cache::delta::{8 cache::delta::{
@@ -11,49 +12,59 @@ use crate::{ · 3 unmodified lines
11 },12 },
12 Item,13 Item,
13 },14 },
15 data,
14 data::EntryRange,16 data::EntryRange,
15};17};
16 18
19pub(crate) struct State<P, F, MBFN, T: Send> {
20 pub bytes_buf: Vec<u8>,
21 pub delta_bytes: Vec<u8>,
22 pub fully_resolved_delta_bytes: Vec<u8>,
23 pub progress: P,
24 pub resolve: F,
25 pub modify_base: MBFN,
26 pub child_items: ItemSliceSend<Item<T>>,
27}
28
17#[allow(clippy::too_many_arguments)]29#[allow(clippy::too_many_arguments)]
18pub(crate) fn deltas<T, F, P, MBFN, S, E>(30pub(crate) fn deltas<T, F, MBFN, E, R, P>(
19 object_counter: Option<gix_features::progress::StepShared>,31 object_counter: Option<gix_features::progress::StepShared>,
20 size_counter: Option<gix_features::progress::StepShared>,32 size_counter: Option<gix_features::progress::StepShared>,
21 node: &mut Item<T>,33 node: &mut Item<T>,
22 (bytes_buf, ref mut progress, state, resolve, modify_base, child_items): &mut (34 State {
23 Vec<u8>,35 bytes_buf,
24 P,36 delta_bytes,
25 S,37 fully_resolved_delta_bytes,
26 F,38 progress,
27 MBFN,39 resolve,
28 ItemSliceSend<Item<T>>,40 modify_base,
29 ),41 child_items,
42 }: &mut State<P, F, MBFN, T>,
43 resolve_data: &R,
30 hash_len: usize,44 hash_len: usize,
45 threads_left: &AtomicIsize,
31 should_interrupt: &AtomicBool,46 should_interrupt: &AtomicBool,
32) -> Result<(), Error>47) -> Result<(), Error>
33where48where
34 T: Send,49 T: Send,
35 F: for<'r> Fn(EntryRange, &'r mut Vec<u8>) -> Option<()>,50 R: Send + Sync,
36 P: Progress,51 P: Progress,
37 MBFN: Fn(&mut T, &mut P, Context<'_, S>) -> Result<(), E>,52 F: for<'r> FnMut(EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
53 MBFN: FnMut(&mut T, &P, Context<'_>) -> Result<(), E> + Send + Clone,
38 E: std::error::Error + Send + Sync + 'static,54 E: std::error::Error + Send + Sync + 'static,
39{55{
40 let mut decompressed_bytes_by_pack_offset = BTreeMap::new();56 let mut decompressed_bytes_by_pack_offset = BTreeMap::new();
41 let bytes_buf = RefCell::new(bytes_buf);57 let mut decompress_from_resolver = |slice: EntryRange, out: &mut Vec<u8>| -> Result<(data::Entry, u64), Error> {
42 let decompress_from_resolver = |slice: EntryRange| -> Result<(crate::data::Entry, u64, Vec<u8>), Error> {58 let bytes = resolve(slice.clone(), resolve_data).ok_or(Error::ResolveFailed {
43 let mut bytes_buf = bytes_buf.borrow_mut();
44 bytes_buf.resize((slice.end - slice.start) as usize, 0);
45 resolve(slice.clone(), &mut bytes_buf).ok_or(Error::ResolveFailed {
46 pack_offset: slice.start,59 pack_offset: slice.start,
47 })?;60 })?;
48 let entry = crate::data::Entry::from_bytes(&bytes_buf, slice.start, hash_len);61 let entry = data::Entry::from_bytes(bytes, slice.start, hash_len);
49 let compressed = &bytes_buf[entry.header_size()..];62 let compressed = &bytes[entry.header_size()..];
50 let decompressed_len = entry.decompressed_size as usize;63 let decompressed_len = entry.decompressed_size as usize;
51 Ok((entry, slice.end, decompress_all_at_once(compressed, decompressed_len)?))64 decompress_all_at_once(compressed, decompressed_len, out)?;
65 Ok((entry, slice.end))
52 };66 };
53 67
54 // Traverse the tree breadth first and loose the data produced for the base as it won't be needed anymore.
55 progress.init(None, gix_features::progress::count_with_decimals("objects", 2));
56
57 // each node is a base, and its children always start out as deltas which become a base after applying them.68 // each node is a base, and its children always start out as deltas which become a base after applying them.
58 // These will be pushed onto our stack until all are processed69 // These will be pushed onto our stack until all are processed
59 let root_level = 0;70 let root_level = 0;
@@ -61,19 +72,26 @@ where · 1 unmodified line
61 root_level,72 root_level,
62 Node {73 Node {
63 item: node,74 item: node,
64 child_items: child_items.0,75 child_items: child_items.clone(),
65 },76 },
66 )];77 )];
67 while let Some((level, mut base)) = nodes.pop() {78 while let Some((level, mut base)) = nodes.pop() {
79 let storage;
68 if should_interrupt.load(Ordering::Relaxed) {80 if should_interrupt.load(Ordering::Relaxed) {
69 return Err(Error::Interrupted);81 return Err(Error::Interrupted);
70 }82 }
71 let (base_entry, entry_end, base_bytes) = if level == root_level {83 let (base_entry, entry_end, base_bytes) = if level == root_level {
72 decompress_from_resolver(base.entry_slice())?84 let (a, b) = decompress_from_resolver(base.entry_slice(), bytes_buf)?;
85 (a, b, &*bytes_buf)
73 } else {86 } else {
74 decompressed_bytes_by_pack_offset87 if !bytes_buf.is_empty() {
88 *bytes_buf = Vec::new();
89 }
90 let (a, b, v) = decompressed_bytes_by_pack_offset
75 .remove(&base.offset())91 .remove(&base.offset())
76 .expect("we store the resolved delta buffer when done")92 .expect("we store the resolved delta buffer when done");
93 storage = v;
94 (a, b, &storage)
77 };95 };
78 96
79 // anything done here must be repeated further down for leaf-nodes.97 // anything done here must be repeated further down for leaf-nodes.
@@ -86,8 +104,7 @@ where · 6 unmodified lines
86 Context {104 Context {
87 entry: &base_entry,105 entry: &base_entry,
88 entry_end,106 entry_end,
89 decompressed: &base_bytes,107 decompressed: base_bytes,
90 state,
91 level,108 level,
92 },109 },
93 )110 )
@@ -99,20 +116,19 @@ where · 5 unmodified lines
99 }116 }
100 117
101 for mut child in base.into_child_iter() {118 for mut child in base.into_child_iter() {
102 let (mut child_entry, entry_end, delta_bytes) = decompress_from_resolver(child.entry_slice())?;119 let (mut child_entry, entry_end) = decompress_from_resolver(child.entry_slice(), delta_bytes)?;
103 let (base_size, consumed) = crate::data::delta::decode_header_size(&delta_bytes);120 let (base_size, consumed) = data::delta::decode_header_size(delta_bytes);
104 let mut header_ofs = consumed;121 let mut header_ofs = consumed;
105 assert_eq!(122 assert_eq!(
106 base_bytes.len(),123 base_bytes.len(),
107 base_size as usize,124 base_size as usize,
108 "recorded base size in delta does not match"125 "recorded base size in delta does match the actual one"
109 );126 );
110 let (result_size, consumed) = crate::data::delta::decode_header_size(&delta_bytes[consumed..]);127 let (result_size, consumed) = data::delta::decode_header_size(&delta_bytes[consumed..]);
111 header_ofs += consumed;128 header_ofs += consumed;
112 129
113 let mut fully_resolved_delta_bytes = bytes_buf.borrow_mut();130 resize_vec(fully_resolved_delta_bytes, result_size as usize);
114 fully_resolved_delta_bytes.resize(result_size as usize, 0);131 data::delta::apply(base_bytes, fully_resolved_delta_bytes, &delta_bytes[header_ofs..]);
115 crate::data::delta::apply(&base_bytes, &mut fully_resolved_delta_bytes, &delta_bytes[header_ofs..]);
116 132
117 // FIXME: this actually invalidates the "pack_offset()" computation, which is not obvious to consumers133 // FIXME: this actually invalidates the "pack_offset()" computation, which is not obvious to consumers
118 // at all134 // at all
@@ -120,7 +136,7 @@ where · 1 unmodified line
120 if child.has_children() {136 if child.has_children() {
121 decompressed_bytes_by_pack_offset.insert(137 decompressed_bytes_by_pack_offset.insert(
122 child.offset(),138 child.offset(),
123 (child_entry, entry_end, fully_resolved_delta_bytes.to_owned()),139 (child_entry, entry_end, std::mem::take(fully_resolved_delta_bytes)),
124 );140 );
125 nodes.push((level + 1, child));141 nodes.push((level + 1, child));
126 } else {142 } else {
@@ -130,8 +146,7 @@ where · 3 unmodified lines
130 Context {146 Context {
131 entry: &child_entry,147 entry: &child_entry,
132 entry_end,148 entry_end,
133 decompressed: &fully_resolved_delta_bytes,149 decompressed: fully_resolved_delta_bytes,
134 state,
135 level: level + 1,150 level: level + 1,
136 },151 },
137 )152 )
@@ -142,25 +157,273 @@ where · 4 unmodified lines
142 .map(|c| c.fetch_add(base_bytes.len(), Ordering::SeqCst));157 .map(|c| c.fetch_add(base_bytes.len(), Ordering::SeqCst));
143 }158 }
144 }159 }
160
161 // After the first round, see if we can use additional threads, and if so we enter multi-threaded mode.
162 // In it we will keep using new threads as they become available while using this thread for coordination.
163 // We optimize for a low memory footprint as we are likely to get here if long delta-chains with large objects are involved.
164 // Try to avoid going into threaded mode if there isn't more than one unit of work anyway.
165 if nodes.len() > 1 {
166 if let Ok(initial_threads) =
167 threads_left.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |threads_available| {
168 (threads_available > 0).then_some(0)
169 })
170 {
171 // Assure no memory is held here.
172 *bytes_buf = Vec::new();
173 *delta_bytes = Vec::new();
174 *fully_resolved_delta_bytes = Vec::new();
175 return deltas_mt(
176 initial_threads,
177 decompressed_bytes_by_pack_offset,
178 object_counter,
179 size_counter,
180 progress,
181 nodes,
182 resolve.clone(),
183 resolve_data,
184 modify_base.clone(),
185 hash_len,
186 threads_left,
187 should_interrupt,
188 );
189 }
190 }
145 }191 }
146 192
147 Ok(())193 Ok(())
148}194}
149 195
150fn decompress_all_at_once(b: &[u8], decompressed_len: usize) -> Result<Vec<u8>, Error> {196/// * `initial_threads` is the threads we may spawn, not accounting for our own thread which is still considered used by the parent
151 let mut out = Vec::with_capacity(decompressed_len);197/// system. Since this thread will take a controlling function, we may spawn one more than that. In threaded mode, we will finish
152 // SAFETY:198/// all remaining work.
153 // 1. we have reserved `decompressed_len`199#[allow(clippy::too_many_arguments)]
154 // 2. zlib is going to write all of `decompressed_len`. On error, none of the the buffer is made available to the user.200pub(crate) fn deltas_mt<T, F, MBFN, E, R, P>(
155 #[allow(unsafe_code, clippy::uninit_vec)]201 mut threads_to_create: isize,
156 unsafe {202 decompressed_bytes_by_pack_offset: BTreeMap<u64, (data::Entry, u64, Vec<u8>)>,
157 out.set_len(decompressed_len);203 object_counter: Option<gix_features::progress::StepShared>,
204 size_counter: Option<gix_features::progress::StepShared>,
205 progress: &P,
206 nodes: Vec<(u16, Node<'_, T>)>,
207 resolve: F,
208 resolve_data: &R,
209 modify_base: MBFN,
210 hash_len: usize,
211 threads_left: &AtomicIsize,
212 should_interrupt: &AtomicBool,
213) -> Result<(), Error>
214where
215 T: Send,
216 R: Send + Sync,
217 P: Progress,
218 F: for<'r> FnMut(EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
219 MBFN: FnMut(&mut T, &P, Context<'_>) -> Result<(), E> + Send + Clone,
220 E: std::error::Error + Send + Sync + 'static,
221{
222 let nodes = gix_features::threading::Mutable::new(nodes);
223 let decompressed_bytes_by_pack_offset = gix_features::threading::Mutable::new(decompressed_bytes_by_pack_offset);
224 threads_to_create += 1; // ourselves
225 let mut returned_ourselves = false;
226
227 gix_features::parallel::threads(|s| -> Result<(), Error> {
228 let mut threads = Vec::new();
229 let poll_interval = std::time::Duration::from_millis(100);
230 loop {
231 for tid in 0..threads_to_create {
232 let thread = gix_features::parallel::build_thread()
233 .name(format!("gix-pack.traverse_deltas.{tid}"))
234 .spawn_scoped(s, {
235 let nodes = &nodes;
236 let decompressed_bytes_by_pack_offset = &decompressed_bytes_by_pack_offset;
237 let mut resolve = resolve.clone();
238 let mut modify_base = modify_base.clone();
239 let object_counter = object_counter.as_ref();
240 let size_counter = size_counter.as_ref();
241
242 move || -> Result<(), Error> {
243 let mut bytes_buf = Vec::new();
244 let mut delta_bytes = Vec::new();
245 let mut fully_resolved_delta_bytes = Vec::new();
246 let mut decompress_from_resolver =
247 |slice: EntryRange, out: &mut Vec<u8>| -> Result<(data::Entry, u64), Error> {
248 let bytes = resolve(slice.clone(), resolve_data).ok_or(Error::ResolveFailed {
249 pack_offset: slice.start,
250 })?;
251 let entry = data::Entry::from_bytes(bytes, slice.start, hash_len);
252 let compressed = &bytes[entry.header_size()..];
253 let decompressed_len = entry.decompressed_size as usize;
254 decompress_all_at_once(compressed, decompressed_len, out)?;
255 Ok((entry, slice.end))
256 };
257
258 loop {
259 let (level, mut base) = match threading::lock(nodes).pop() {
260 Some(v) => v,
261 None => break,
262 };
263 if should_interrupt.load(Ordering::Relaxed) {
264 return Err(Error::Interrupted);
265 }
266 let storage;
267 let (base_entry, entry_end, base_bytes) = if level == 0 {
268 let (a, b) = decompress_from_resolver(base.entry_slice(), &mut bytes_buf)?;
269 (a, b, &*bytes_buf)
270 } else {
271 if !bytes_buf.is_empty() {
272 bytes_buf = Vec::new();
273 }
274 let (a, b, v) = threading::lock(decompressed_bytes_by_pack_offset)
275 .remove(&base.offset())
276 .expect("we store the resolved delta buffer when done");
277 storage = v;
278 (a, b, storage.as_slice())
279 };
280
281 // anything done here must be repeated further down for leaf-nodes.
282 // This way we avoid retaining their decompressed memory longer than needed (they have no children,
283 // thus their memory can be released right away, using 18% less peak memory on the linux kernel).
284 {
285 modify_base(
286 base.data(),
287 progress,
288 Context {
289 entry: &base_entry,
290 entry_end,
291 decompressed: base_bytes,
292 level,
293 },
294 )
295 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
296 object_counter.as_ref().map(|c| c.fetch_add(1, Ordering::SeqCst));
297 size_counter
298 .as_ref()
299 .map(|c| c.fetch_add(base_bytes.len(), Ordering::SeqCst));
300 }
301
302 for mut child in base.into_child_iter() {
303 let (mut child_entry, entry_end) =
304 decompress_from_resolver(child.entry_slice(), &mut delta_bytes)?;
305 let (base_size, consumed) = data::delta::decode_header_size(&delta_bytes);
306 let mut header_ofs = consumed;
307 assert_eq!(
308 base_bytes.len(),
309 base_size as usize,
310 "recorded base size in delta does match the actual one"
311 );
312 let (result_size, consumed) =
313 data::delta::decode_header_size(&delta_bytes[consumed..]);
314 header_ofs += consumed;
315
316 fully_resolved_delta_bytes.resize(result_size as usize, 0);
317 data::delta::apply(
318 base_bytes,
319 &mut fully_resolved_delta_bytes,
320 &delta_bytes[header_ofs..],
321 );
322
323 // FIXME: this actually invalidates the "pack_offset()" computation, which is not obvious to consumers
324 // at all
325 child_entry.header = base_entry.header; // assign the actual object type, instead of 'delta'
326 if child.has_children() {
327 threading::lock(decompressed_bytes_by_pack_offset).insert(
328 child.offset(),
329 (child_entry, entry_end, std::mem::take(&mut fully_resolved_delta_bytes)),
330 );
331 threading::lock(nodes).push((level + 1, child));
332 } else {
333 modify_base(
334 child.data(),
335 progress,
336 Context {
337 entry: &child_entry,
338 entry_end,
339 decompressed: &fully_resolved_delta_bytes,
340 level: level + 1,
341 },
342 )
343 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
344 object_counter.as_ref().map(|c| c.fetch_add(1, Ordering::SeqCst));
345 size_counter
346 .as_ref()
347 .map(|c| c.fetch_add(base_bytes.len(), Ordering::SeqCst));
348 }
349 }
350 }
351 Ok(())
352 }
353 })?;
354 threads.push(thread);
355 }
356 if threads_left
357 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |threads_available: isize| {
358 (threads_available > 0).then(|| {
359 threads_to_create = threads_available.min(threading::lock(&nodes).len() as isize);
360 threads_available - threads_to_create
361 })
362 })
363 .is_err()
364 {
365 threads_to_create = 0;
366 }
367
368 // What we really want to do is either wait for one of our threads to go down
369 // or for another scheduled thread to become available. Unfortunately we can't do that,
370 // but may instead find a good way to set the polling interval instead of hard-coding it.
371 std::thread::sleep(poll_interval);
372 // Get out of threads are already starving or they would be starving soon as no work is left.
373 if threads.iter().any(|t| t.is_finished()) {
374 let mut running_threads = Vec::new();
375 for thread in threads.drain(..) {
376 if thread.is_finished() {
377 match thread.join() {
378 Ok(Err(err)) => return Err(err),
379 Ok(Ok(())) => {
380 if !returned_ourselves {
381 returned_ourselves = true;
382 } else {
383 threads_left.fetch_add(1, Ordering::SeqCst);
384 }
385 }
386 Err(err) => {
387 std::panic::resume_unwind(err);
388 }
389 }
390 } else {
391 running_threads.push(thread);
392 }
393 }
394 if running_threads.is_empty() && threading::lock(&nodes).is_empty() {
395 break;
396 }
397 threads = running_threads;
398 }
399 }
400
401 Ok(())
402 })
403}
404
405fn resize_vec(v: &mut Vec<u8>, new_len: usize) {
406 if new_len > v.len() {
407 v.reserve_exact(new_len.saturating_sub(v.capacity()) + (v.capacity() - v.len()));
408 // SAFETY:
409 // 1. we have reserved enough capacity to fit `new_len`
410 // 2. the caller is trusted to write into `v` to completely fill `new_len`.
411 #[allow(unsafe_code, clippy::uninit_vec)]
412 unsafe {
413 v.set_len(new_len);
414 }
415 } else {
416 v.truncate(new_len)
158 }417 }
418}
419
420fn decompress_all_at_once(b: &[u8], decompressed_len: usize, out: &mut Vec<u8>) -> Result<(), Error> {
421 resize_vec(out, decompressed_len);
159 zlib::Inflate::default()422 zlib::Inflate::default()
160 .once(b, &mut out)423 .once(b, out)
161 .map_err(|err| Error::ZlibInflate {424 .map_err(|err| Error::ZlibInflate {
162 source: err,425 source: err,
163 message: "Failed to decompress entry",426 message: "Failed to decompress entry",
164 })?;427 })?;
165 Ok(out)428 Ok(())
166}429}
gix-pack/src/cache/delta/traverse/util.rs+7−5
@@ -4,6 +4,8 @@ pub struct ItemSliceSend<T>(pub *mut [T]) · 3 unmodified lines
4where4where
5 T: Send;5 T: Send;
6 6
7/// SAFETY: This would be unsafe if this would ever be abused, but it's used internally and only in a way that assure that the pointers
8/// don't violate aliasing rules.
7impl<T> Clone for ItemSliceSend<T>9impl<T> Clone for ItemSliceSend<T>
8where10where
9 T: Send,11 T: Send,
@@ -18,12 +20,12 @@ where · 8 unmodified lines
18unsafe impl<T> Send for ItemSliceSend<T> where T: Send {}20unsafe impl<T> Send for ItemSliceSend<T> where T: Send {}
19 21
20/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].22/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].
21pub struct Node<'a, T> {23pub struct Node<'a, T: Send> {
22 pub item: &'a mut Item<T>,24 pub item: &'a mut Item<T>,
23 pub child_items: *mut [Item<T>],25 pub child_items: ItemSliceSend<Item<T>>,
24}26}
25 27
26impl<'a, T> Node<'a, T> {28impl<'a, T: Send> Node<'a, T> {
27 /// Returns the offset into the pack at which the `Node`s data is located.29 /// Returns the offset into the pack at which the `Node`s data is located.
28 pub fn offset(&self) -> u64 {30 pub fn offset(&self) -> u64 {
29 self.item.offset31 self.item.offset
@@ -55,8 +57,8 @@ impl<'a, T> Node<'a, T> { · 25 unmodified lines
55 // SAFETY: The resulting mutable pointer cannot be yielded by any other node.57 // SAFETY: The resulting mutable pointer cannot be yielded by any other node.
56 #[allow(unsafe_code)]58 #[allow(unsafe_code)]
57 Node {59 Node {
58 item: unsafe { &mut *(children as *mut Item<T>).add(index as usize) },60 item: &mut unsafe { &mut *children.0 }[index as usize],
59 child_items: children,61 child_items: children.clone(),
60 }62 }
61 })63 })
62 }64 }
gix-pack/src/index/traverse/mod.rs+18−22
@@ -1,6 +1,9 @@
1use std::sync::atomic::AtomicBool;1use std::sync::atomic::AtomicBool;
2 2
3use gix_features::{parallel, progress::Progress};3use gix_features::{
4 parallel,
5 progress::{Progress, RawProgress},
6};
4 7
5use crate::index;8use crate::index;
6 9
@@ -79,7 +82,7 @@ impl index::File { · 72 unmodified lines
79 pack: &crate::data::File,82 pack: &crate::data::File,
80 progress: P,83 progress: P,
81 should_interrupt: &AtomicBool,84 should_interrupt: &AtomicBool,
82 new_processor: impl Fn() -> Processor + Send + Clone,85 processor: Processor,
83 Options {86 Options {
84 traversal,87 traversal,
85 thread_limit,88 thread_limit,
@@ -91,17 +94,12 @@ impl index::File { · 5 unmodified lines
91 P: Progress,94 P: Progress,
92 C: crate::cache::DecodeEntry,95 C: crate::cache::DecodeEntry,
93 E: std::error::Error + Send + Sync + 'static,96 E: std::error::Error + Send + Sync + 'static,
94 Processor: FnMut(97 Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn RawProgress) -> Result<(), E> + Send + Clone,
95 gix_object::Kind,
96 &[u8],
97 &index::Entry,
98 &mut <P::SubProgress as Progress>::SubProgress,
99 ) -> Result<(), E>,
100 F: Fn() -> C + Send + Clone,98 F: Fn() -> C + Send + Clone,
101 {99 {
102 match traversal {100 match traversal {
103 Algorithm::Lookup => self.traverse_with_lookup(101 Algorithm::Lookup => self.traverse_with_lookup(
104 new_processor,102 processor,
105 pack,103 pack,
106 progress,104 progress,
107 should_interrupt,105 should_interrupt,
@@ -113,10 +111,10 @@ impl index::File { · 5 unmodified lines
113 ),111 ),
114 Algorithm::DeltaTreeLookup => self.traverse_with_index(112 Algorithm::DeltaTreeLookup => self.traverse_with_index(
115 pack,113 pack,
116 new_processor,114 processor,
117 progress,115 progress,
118 should_interrupt,116 should_interrupt,
119 crate::index::traverse::with_index::Options { check, thread_limit },117 with_index::Options { check, thread_limit },
120 ),118 ),
121 }119 }
122 }120 }
@@ -151,19 +149,18 @@ impl index::File { · 28 unmodified lines
151 }149 }
152 150
153 #[allow(clippy::too_many_arguments)]151 #[allow(clippy::too_many_arguments)]
154 fn decode_and_process_entry<C, P, E>(152 fn decode_and_process_entry<C, E>(
155 &self,153 &self,
156 check: SafetyCheck,154 check: SafetyCheck,
157 pack: &crate::data::File,155 pack: &crate::data::File,
158 cache: &mut C,156 cache: &mut C,
159 buf: &mut Vec<u8>,157 buf: &mut Vec<u8>,
160 progress: &mut P,158 progress: &mut dyn RawProgress,
161 index_entry: &crate::index::Entry,159 index_entry: &index::Entry,
162 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &mut P) -> Result<(), E>,160 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn RawProgress) -> Result<(), E>,
163 ) -> Result<crate::data::decode::entry::Outcome, Error<E>>161 ) -> Result<crate::data::decode::entry::Outcome, Error<E>>
164 where162 where
165 C: crate::cache::DecodeEntry,163 C: crate::cache::DecodeEntry,
166 P: Progress,
167 E: std::error::Error + Send + Sync + 'static,164 E: std::error::Error + Send + Sync + 'static,
168 {165 {
169 let pack_entry = pack.entry(index_entry.pack_offset);166 let pack_entry = pack.entry(index_entry.pack_offset);
@@ -192,9 +189,9 @@ impl index::File { · 22 unmodified lines
192 check,189 check,
193 object_kind,190 object_kind,
194 buf,191 buf,
195 progress,
196 index_entry,192 index_entry,
197 || pack.entry_crc32(index_entry.pack_offset, entry_len),193 || pack.entry_crc32(index_entry.pack_offset, entry_len),
194 progress,
198 processor,195 processor,
199 )?;196 )?;
200 Ok(entry_stats)197 Ok(entry_stats)
@@ -202,17 +199,16 @@ impl index::File { · 1 unmodified line
202}199}
203 200
204#[allow(clippy::too_many_arguments)]201#[allow(clippy::too_many_arguments)]
205fn process_entry<P, E>(202fn process_entry<E>(
206 check: SafetyCheck,203 check: SafetyCheck,
207 object_kind: gix_object::Kind,204 object_kind: gix_object::Kind,
208 decompressed: &[u8],205 decompressed: &[u8],
209 progress: &mut P,206 index_entry: &index::Entry,
210 index_entry: &crate::index::Entry,
211 pack_entry_crc32: impl FnOnce() -> u32,207 pack_entry_crc32: impl FnOnce() -> u32,
212 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &mut P) -> Result<(), E>,208 progress: &dyn RawProgress,
209 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn RawProgress) -> Result<(), E>,
213) -> Result<(), Error<E>>210) -> Result<(), Error<E>>
214where211where
215 P: Progress,
216 E: std::error::Error + Send + Sync + 'static,212 E: std::error::Error + Send + Sync + 'static,
217{213{
218 if check.object_checksum() {214 if check.object_checksum() {
gix-pack/src/index/traverse/with_index.rs+16−20
@@ -59,19 +59,16 @@ impl index::File { · 58 unmodified lines
59 pub fn traverse_with_index<P, Processor, E>(59 pub fn traverse_with_index<P, Processor, E>(
60 &self,60 &self,
61 pack: &crate::data::File,61 pack: &crate::data::File,
62 new_processor: impl Fn() -> Processor + Send + Clone,62 mut processor: Processor,
63 mut progress: P,63 mut progress: P,
64 should_interrupt: &AtomicBool,64 should_interrupt: &AtomicBool,
65 Options { check, thread_limit }: Options,65 Options { check, thread_limit }: Options,
66 ) -> Result<Outcome<P>, Error<E>>66 ) -> Result<Outcome<P>, Error<E>>
67 where67 where
68 P: Progress,68 P: Progress,
69 Processor: FnMut(69 Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn gix_features::progress::RawProgress) -> Result<(), E>
70 gix_object::Kind,70 + Send
71 &[u8],71 + Clone,
72 &index::Entry,
73 &mut <P::SubProgress as Progress>::SubProgress,
74 ) -> Result<(), E>,
75 E: std::error::Error + Send + Sync + 'static,72 E: std::error::Error + Send + Sync + 'static,
76 {73 {
77 let (verify_result, traversal_result) = parallel::join(74 let (verify_result, traversal_result) = parallel::join(
@@ -113,18 +110,17 @@ impl index::File { · 35 unmodified lines
113 self.object_hash,110 self.object_hash,
114 )?;111 )?;
115 let mut outcome = digest_statistics(tree.traverse(112 let mut outcome = digest_statistics(tree.traverse(
116 |slice, out| pack.entry_slice(slice).map(|entry| out.copy_from_slice(entry)),113 |slice, pack| pack.entry_slice(slice),
114 pack,
117 pack.pack_end() as u64,115 pack.pack_end() as u64,
118 new_processor,116 move |data,
119 |data,117 progress,
120 progress,118 traverse::Context {
121 traverse::Context {119 entry: pack_entry,
122 entry: pack_entry,120 entry_end,
123 entry_end,121 decompressed: bytes,
124 decompressed: bytes,122 level,
125 state: ref mut processor,123 }| {
126 level,
127 }| {
128 let object_kind = pack_entry.header.as_kind().expect("non-delta object");124 let object_kind = pack_entry.header.as_kind().expect("non-delta object");
129 data.level = level;125 data.level = level;
130 data.decompressed_size = pack_entry.decompressed_size;126 data.decompressed_size = pack_entry.decompressed_size;
@@ -135,7 +131,6 @@ impl index::File { · 4 unmodified lines
135 check,131 check,
136 object_kind,132 object_kind,
137 bytes,133 bytes,
138 progress,
139 &data.index_entry,134 &data.index_entry,
140 || {135 || {
141 // TODO: Fix this - we overwrite the header of 'data' which also changes the computed entry size,136 // TODO: Fix this - we overwrite the header of 'data' which also changes the computed entry size,
@@ -146,7 +141,8 @@ impl index::File { · 4 unmodified lines
146 .expect("slice pointing into the pack (by now data is verified)"),141 .expect("slice pointing into the pack (by now data is verified)"),
147 )142 )
148 },143 },
149 processor,144 progress,
145 &mut processor,
150 );146 );
151 match result {147 match result {
152 Err(err @ Error::PackDecode { .. }) if !check.fatal_decode_error() => {148 Err(err @ Error::PackDecode { .. }) if !check.fatal_decode_error() => {
gix-pack/src/index/traverse/with_lookup.rs+8−12
@@ -67,7 +67,7 @@ impl index::File { · 66 unmodified lines
67 /// For more details, see the documentation on the [`traverse()`][index::File::traverse()] method.67 /// For more details, see the documentation on the [`traverse()`][index::File::traverse()] method.
68 pub fn traverse_with_lookup<P, C, Processor, E, F>(68 pub fn traverse_with_lookup<P, C, Processor, E, F>(
69 &self,69 &self,
70 new_processor: impl Fn() -> Processor + Send + Clone,70 mut processor: Processor,
71 pack: &data::File,71 pack: &data::File,
72 mut progress: P,72 mut progress: P,
73 should_interrupt: &AtomicBool,73 should_interrupt: &AtomicBool,
@@ -81,12 +81,9 @@ impl index::File { · 7 unmodified lines
81 P: Progress,81 P: Progress,
82 C: crate::cache::DecodeEntry,82 C: crate::cache::DecodeEntry,
83 E: std::error::Error + Send + Sync + 'static,83 E: std::error::Error + Send + Sync + 'static,
84 Processor: FnMut(84 Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn gix_features::progress::RawProgress) -> Result<(), E>
85 gix_object::Kind,85 + Send
86 &[u8],86 + Clone,
87 &index::Entry,
88 &mut <P::SubProgress as Progress>::SubProgress,
89 ) -> Result<(), E>,
90 F: Fn() -> C + Send + Clone,87 F: Fn() -> C + Send + Clone,
91 {88 {
92 let (verify_result, traversal_result) = parallel::join(89 let (verify_result, traversal_result) = parallel::join(
@@ -133,7 +130,6 @@ impl index::File { · 40 unmodified lines
133 move |index| {130 move |index| {
134 (131 (
135 make_pack_lookup_cache(),132 make_pack_lookup_cache(),
136 new_processor(),
137 Vec::with_capacity(2048), // decode buffer133 Vec::with_capacity(2048), // decode buffer
138 lock(&reduce_progress)134 lock(&reduce_progress)
139 .add_child_with_id(format!("thread {index}"), gix_features::progress::UNKNOWN), // per thread progress135 .add_child_with_id(format!("thread {index}"), gix_features::progress::UNKNOWN), // per thread progress
@@ -146,9 +142,9 @@ impl index::File { · 6 unmodified lines
146 input_chunks,142 input_chunks,
147 thread_limit,143 thread_limit,
148 state_per_thread,144 state_per_thread,
149 |entries: &[index::Entry],145 move |entries: &[index::Entry],
150 (cache, ref mut processor, buf, progress)|146 (cache, buf, progress)|
151 -> Result<Vec<data::decode::entry::Outcome>, Error<_>> {147 -> Result<Vec<data::decode::entry::Outcome>, Error<_>> {
152 progress.init(148 progress.init(
153 Some(entries.len()),149 Some(entries.len()),
154 gix_features::progress::count_with_decimals("objects", 2),150 gix_features::progress::count_with_decimals("objects", 2),
@@ -163,7 +159,7 @@ impl index::File { · 8 unmodified lines
163 buf,159 buf,
164 progress,160 progress,
165 index_entry,161 index_entry,
166 processor,162 &mut processor,
167 );163 );
168 progress.inc();164 progress.inc();
169 let stat = match result {165 let stat = match result {
gix-pack/src/index/verify.rs+5−8
@@ -198,7 +198,7 @@ impl index::File { · 197 unmodified lines
198 pack,198 pack,
199 progress,199 progress,
200 should_interrupt,200 should_interrupt,
201 || {201 {
202 let mut encode_buf = Vec::with_capacity(2048);202 let mut encode_buf = Vec::with_capacity(2048);
203 move |kind, data, index_entry, progress| {203 move |kind, data, index_entry, progress| {
204 Self::verify_entry(verify_mode, &mut encode_buf, kind, data, index_entry, progress)204 Self::verify_entry(verify_mode, &mut encode_buf, kind, data, index_entry, progress)
@@ -231,17 +231,14 @@ impl index::File { · 26 unmodified lines
231 }231 }
232 232
233 #[allow(clippy::too_many_arguments)]233 #[allow(clippy::too_many_arguments)]
234 fn verify_entry<P>(234 fn verify_entry(
235 verify_mode: Mode,235 verify_mode: Mode,
236 encode_buf: &mut Vec<u8>,236 encode_buf: &mut Vec<u8>,
237 object_kind: gix_object::Kind,237 object_kind: gix_object::Kind,
238 buf: &[u8],238 buf: &[u8],
239 index_entry: &index::Entry,239 index_entry: &index::Entry,
240 progress: &mut P,240 progress: &dyn gix_features::progress::RawProgress,
241 ) -> Result<(), integrity::Error>241 ) -> Result<(), integrity::Error> {
242 where
243 P: Progress,
244 {
245 if let Mode::HashCrc32Decode | Mode::HashCrc32DecodeEncode = verify_mode {242 if let Mode::HashCrc32Decode | Mode::HashCrc32DecodeEncode = verify_mode {
246 use gix_object::Kind::*;243 use gix_object::Kind::*;
247 match object_kind {244 match object_kind {
@@ -260,7 +257,7 @@ impl index::File { · 12 unmodified lines
260 .expect("writing to a memory buffer never fails");257 .expect("writing to a memory buffer never fails");
261 if encode_buf.as_slice() != buf {258 if encode_buf.as_slice() != buf {
262 let mut should_return_error = true;259 let mut should_return_error = true;
263 if let gix_object::Kind::Tree = object_kind {260 if let Tree = object_kind {
264 if buf.as_bstr().find(b"100664").is_some() || buf.as_bstr().find(b"100640").is_some() {261 if buf.as_bstr().find(b"100664").is_some() || buf.as_bstr().find(b"100640").is_some() {
265 progress.info(format!("Tree object {} would be cleaned up during re-serialization, replacing mode '100664|100640' with '100644'", index_entry.oid));262 progress.info(format!("Tree object {} would be cleaned up during re-serialization, replacing mode '100664|100640' with '100644'", index_entry.oid));
266 should_return_error = false263 should_return_error = false
gix-pack/src/index/write/mod.rs+9−14
@@ -83,20 +83,22 @@ impl crate::index::File { · 82 unmodified lines
83 /// It should return `None` if the entry cannot be resolved from the pack that produced the `entries` iterator, causing83 /// It should return `None` if the entry cannot be resolved from the pack that produced the `entries` iterator, causing
84 /// the write operation to fail.84 /// the write operation to fail.
85 #[allow(clippy::too_many_arguments)]85 #[allow(clippy::too_many_arguments)]
86 pub fn write_data_iter_to_stream<F, F2>(86 pub fn write_data_iter_to_stream<F, F2, R, P>(
87 version: crate::index::Version,87 version: crate::index::Version,
88 make_resolver: F,88 make_resolver: F,
89 entries: impl Iterator<Item = Result<crate::data::input::Entry, crate::data::input::Error>>,89 entries: impl Iterator<Item = Result<crate::data::input::Entry, crate::data::input::Error>>,
90 thread_limit: Option<usize>,90 thread_limit: Option<usize>,
91 mut root_progress: impl Progress,91 mut root_progress: P,
92 out: impl io::Write,92 out: impl io::Write,
93 should_interrupt: &AtomicBool,93 should_interrupt: &AtomicBool,
94 object_hash: gix_hash::Kind,94 object_hash: gix_hash::Kind,
95 pack_version: crate::data::Version,95 pack_version: crate::data::Version,
96 ) -> Result<Outcome, Error>96 ) -> Result<Outcome, Error>
97 where97 where
98 F: FnOnce() -> io::Result<F2>,98 F: FnOnce() -> io::Result<(F2, R)>,
99 F2: for<'r> Fn(crate::data::EntryRange, &'r mut Vec<u8>) -> Option<()> + Send + Clone,99 R: Send + Sync,
100 F2: for<'r> Fn(crate::data::EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
101 P: Progress,
100 {102 {
101 if version != crate::index::Version::default() {103 if version != crate::index::Version::default() {
102 return Err(Error::Unsupported(version));104 return Err(Error::Unsupported(version));
@@ -180,12 +182,12 @@ impl crate::index::File { · 77 unmodified lines
180 182
181 root_progress.inc();183 root_progress.inc();
182 184
183 let resolver = make_resolver()?;185 let (resolver, pack) = make_resolver()?;
184 let sorted_pack_offsets_by_oid = {186 let sorted_pack_offsets_by_oid = {
185 let traverse::Outcome { roots, children } = tree.traverse(187 let traverse::Outcome { roots, children } = tree.traverse(
186 resolver,188 resolver,
189 &pack,
187 pack_entries_end,190 pack_entries_end,
188 || (),
189 |data,191 |data,
190 _progress,192 _progress,
191 traverse::Context {193 traverse::Context {
@@ -250,14 +252,7 @@ impl crate::index::File { · 58 unmodified lines
250}252}
251 253
252fn modify_base(entry: &mut TreeEntry, pack_entry: &crate::data::Entry, decompressed: &[u8], hash: gix_hash::Kind) {254fn modify_base(entry: &mut TreeEntry, pack_entry: &crate::data::Entry, decompressed: &[u8], hash: gix_hash::Kind) {
253 fn compute_hash(kind: gix_object::Kind, bytes: &[u8], object_hash: gix_hash::Kind) -> gix_hash::ObjectId {
254 let mut hasher = gix_features::hash::hasher(object_hash);
255 hasher.update(&gix_object::encode::loose_header(kind, bytes.len()));
256 hasher.update(bytes);
257 gix_hash::ObjectId::from(hasher.digest())
258 }
259
260 let object_kind = pack_entry.header.as_kind().expect("base object as source of iteration");255 let object_kind = pack_entry.header.as_kind().expect("base object as source of iteration");
261 let id = compute_hash(object_kind, decompressed, hash);256 let id = gix_object::compute_hash(hash, object_kind, decompressed);
262 entry.id = id;257 entry.id = id;
263}258}
gix-pack/tests/pack/index.rs+16−27
@@ -115,28 +115,25 @@ mod version { · 114 unmodified lines
115 115
116 use gix_features::progress;116 use gix_features::progress;
117 use gix_odb::pack;117 use gix_odb::pack;
118 use gix_pack::{118 use gix_pack::{data::input, index};
119 data::{input, EntryRange},
120 index,
121 };
122 119
123 use crate::{120 use crate::{
124 fixture_path,121 fixture_path,
125 pack::{INDEX_V2, V2_PACKS_AND_INDICES},122 pack::{INDEX_V2, V2_PACKS_AND_INDICES},
126 };123 };
127 124
125 fn slice_map(entry: gix_pack::data::EntryRange, map: &memmap2::Mmap) -> Option<&[u8]> {
126 map.get(entry.start as usize..entry.end as usize)
127 }
128
128 #[test]129 #[test]
129 fn write_to_stream() -> Result<(), Box<dyn std::error::Error>> {130 fn write_to_stream() -> Result<(), Box<dyn std::error::Error>> {
130 fn assert_index_write<F>(131 fn assert_index_write(
131 mode: &input::Mode,132 mode: &input::Mode,
132 compressed: &input::EntryDataMode,133 compressed: &input::EntryDataMode,
133 index_path: &&str,134 index_path: &&str,
134 data_path: &&str,135 data_path: &&str,
135 resolve: F,136 ) -> Result<(), Box<dyn std::error::Error>> {
136 ) -> Result<(), Box<dyn std::error::Error>>
137 where
138 F: Fn(pack::data::EntryRange, &mut Vec<u8>) -> Option<()> + Send + Clone,
139 {
140 let pack_iter = pack::data::input::BytesToEntriesIter::new_from_header(137 let pack_iter = pack::data::input::BytesToEntriesIter::new_from_header(
141 io::BufReader::new(fs::File::open(fixture_path(data_path))?),138 io::BufReader::new(fs::File::open(fixture_path(data_path))?),
142 *mode,139 *mode,
@@ -150,7 +147,11 @@ mod version { · 7 unmodified lines
150 let pack_version = pack_iter.version();147 let pack_version = pack_iter.version();
151 let outcome = pack::index::File::write_data_iter_to_stream(148 let outcome = pack::index::File::write_data_iter_to_stream(
152 desired_kind,149 desired_kind,
153 move || Ok(resolve),150 || {
151 let file = std::fs::File::open(fixture_path(data_path))?;
152 let map = unsafe { memmap2::Mmap::map(&file)? };
153 Ok((slice_map, map))
154 },
154 pack_iter,155 pack_iter,
155 None,156 None,
156 progress::Discard,157 progress::Discard,
@@ -217,17 +218,7 @@ mod version { · 60 unmodified lines
217 for mode in &[input::Mode::AsIs, input::Mode::Verify, input::Mode::Restore] {218 for mode in &[input::Mode::AsIs, input::Mode::Verify, input::Mode::Restore] {
218 for compressed in &[input::EntryDataMode::Crc32, input::EntryDataMode::KeepAndCrc32] {219 for compressed in &[input::EntryDataMode::Crc32, input::EntryDataMode::KeepAndCrc32] {
219 for (index_path, data_path) in V2_PACKS_AND_INDICES {220 for (index_path, data_path) in V2_PACKS_AND_INDICES {
220 let resolve = {221 assert_index_write(mode, compressed, index_path, data_path)?;
221 let buf = gix_features::threading::OwnShared::new({
222 let file = std::fs::File::open(fixture_path(data_path))?;
223 unsafe { memmap2::Mmap::map(&file)? }
224 });
225 move |entry: EntryRange, out: &mut Vec<u8>| {
226 buf.get(entry.start as usize..entry.end as usize)
227 .map(|slice| out.copy_from_slice(slice))
228 }
229 };
230 assert_index_write(mode, compressed, index_path, data_path, resolve)?;
231 }222 }
232 }223 }
233 }224 }
@@ -259,11 +250,9 @@ fn traverse_with_index_and_forward_ref_deltas() { · 25 unmodified lines
259 let _it_should_work = index250 let _it_should_work = index
260 .traverse_with_index(251 .traverse_with_index(
261 &data,252 &data,
262 || {253 |_, _, _, _| {
263 |_, _, _, _| {254 count.fetch_add(1, Ordering::SeqCst);
264 count.fetch_add(1, Ordering::SeqCst);255 Ok::<_, std::io::Error>(())
265 Ok::<_, std::io::Error>(())
266 }
267 },256 },
268 progress::Discard,257 progress::Discard,
269 &AtomicBool::new(false),258 &AtomicBool::new(false),