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.
Unified · Split
| @@ -5,7 +5,7 @@ repository = "https://github.com/Byron/gitoxide" · 4 unmodified lines | ||
| 5 | 5 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] |
| 6 | 6 | license = "MIT/Apache-2.0" |
| 7 | 7 | description = "Implements git packs and related data structures" |
| 8 | −edition = "2018" | |
| 8 | +edition = "2021" | |
| 9 | 9 | include = ["src/**/*", "CHANGELOG.md"] |
| 10 | 10 | rust-version = "1.64" |
| 11 | 11 | autotests = false |
| @@ -62,17 +62,14 @@ impl crate::Bundle { · 61 unmodified lines | ||
| 62 | 62 | /// * 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 | 63 | /// be accounted for. |
| 64 | 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 | 66 | pack: impl io::BufRead, |
| 67 | 67 | directory: Option<impl AsRef<Path>>, |
| 68 | − mut progress: P, | |
| 68 | + mut progress: impl Progress, | |
| 69 | 69 | should_interrupt: &AtomicBool, |
| 70 | 70 | thin_pack_base_object_lookup_fn: Option<ThinPackLookupFn>, |
| 71 | 71 | options: Options, |
| 72 | − ) -> Result<Outcome, Error> | |
| 73 | − where | |
| 74 | − P: Progress, | |
| 75 | − { | |
| 72 | + ) -> Result<Outcome, Error> { | |
| 76 | 73 | let mut read_progress = progress.add_child_with_id("read pack", ProgressId::ReadPackBytes.into()); |
| 77 | 74 | read_progress.init(None, progress::bytes()); |
| 78 | 75 | let pack = progress::Read { |
| @@ -354,20 +351,20 @@ impl crate::Bundle { · 275 unmodified lines | ||
| 354 | 351 | } |
| 355 | 352 | } |
| 356 | 353 | |
| 354 | +fn 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 | + | |
| 357 | 358 | fn new_pack_file_resolver( |
| 358 | 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 | 364 | let mut guard = data_file.lock(); |
| 361 | 365 | guard.flush()?; |
| 362 | − let mapped_file = Arc::new(crate::mmap::read_only( | |
| 363 | − &guard.get_mut().with_mut(|f| f.path().to_owned())?, | |
| 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) | |
| 366 | + let mapped_file = crate::mmap::read_only(&guard.get_mut().with_mut(|f| f.path().to_owned())?)?; | |
| 367 | + Ok((resolve_entry, mapped_file)) | |
| 371 | 368 | } |
| 372 | 369 | |
| 373 | 370 | struct WriteOutcome { |
| @@ -1,9 +1,10 @@ | ||
| 1 | 1 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 2 | 2 | |
| 3 | +use gix_features::threading::{Mutable, OwnShared}; | |
| 3 | 4 | use gix_features::{ |
| 4 | 5 | parallel::in_parallel_with_slice, |
| 5 | 6 | progress::{self, Progress}, |
| 6 | − threading::{lock, Mutable, OwnShared}, | |
| 7 | + threading, | |
| 7 | 8 | }; |
| 8 | 9 | |
| 9 | 10 | use crate::{ |
| @@ -36,18 +37,18 @@ pub enum Error { · 26 unmodified lines | ||
| 36 | 37 | /// The base's offset which was from a resolved ref-delta that didn't actually get added to the tree |
| 37 | 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 | 44 | /// Additional context passed to the `inspect_object(…)` function of the [`Tree::traverse()`] method. |
| 42 | −pub struct Context<'a, S> { | |
| 45 | +pub struct Context<'a> { | |
| 43 | 46 | /// The pack entry describing the object |
| 44 | 47 | pub entry: &'a crate::data::Entry, |
| 45 | 48 | /// The offset at which `entry` ends in the pack, useful to learn about the exact range of `entry` within the pack. |
| 46 | 49 | pub entry_end: u64, |
| 47 | 50 | /// The decompressed object itself, ready to be decoded. |
| 48 | 51 | pub decompressed: &'a [u8], |
| 49 | − /// Custom state known to the function | |
| 50 | − pub state: &'a mut S, | |
| 51 | 52 | /// The depth at which this object resides in the delta-tree. It represents the amount of base objects, with 0 indicating |
| 52 | 53 | /// an 'undeltified' object, and higher values indicating delta objects with the given amount of bases. |
| 53 | 54 | pub level: u16, |
| @@ -89,79 +90,86 @@ where · 35 unmodified lines | ||
| 89 | 90 | /// operation as well. |
| 90 | 91 | /// * `pack_entries_end` marks one-past-the-last byte of the last entry in the pack, as the last entries size would otherwise |
| 91 | 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 | 93 | /// * `inspect_object(node_data: &mut T, progress: Progress, context: Context<ThreadLocal State>) -> Result<(), CustomError>` is a function |
| 94 | 94 | /// running for each thread receiving fully decoded objects along with contextual information, which either succeeds with `Ok(())` |
| 95 | 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 | 99 | /// This method returns a vector of all tree items, along with their potentially modified custom node data. |
| 99 | 100 | /// |
| 100 | 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 | 103 | mut self, |
| 103 | 104 | resolve: F, |
| 105 | + resolve_data: &R, | |
| 104 | 106 | pack_entries_end: u64, |
| 105 | − new_thread_state: impl Fn() -> S + Send + Clone, | |
| 106 | 107 | inspect_object: MBFN, |
| 107 | 108 | Options { |
| 108 | 109 | thread_limit, |
| 109 | − object_progress, | |
| 110 | + mut object_progress, | |
| 110 | 111 | mut size_progress, |
| 111 | 112 | should_interrupt, |
| 112 | 113 | object_hash, |
| 113 | 114 | }: Options<'_, P1, P2>, |
| 114 | 115 | ) -> Result<Outcome<T>, Error> |
| 115 | 116 | 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 | 119 | P1: Progress, |
| 118 | 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 | 122 | E: std::error::Error + Send + Sync + 'static, |
| 121 | 123 | { |
| 122 | 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 | 126 | let num_objects = self.num_items(); |
| 126 | 127 | let object_counter = { |
| 127 | − let mut progress = lock(&object_progress); | |
| 128 | + let progress = &mut object_progress; | |
| 128 | 129 | progress.init(Some(num_objects), progress::count("objects")); |
| 129 | 130 | progress.counter() |
| 130 | 131 | }; |
| 131 | 132 | size_progress.init(None, progress::bytes()); |
| 132 | 133 | let size_counter = size_progress.counter(); |
| 133 | 134 | let child_items = self.child_items.as_mut_slice(); |
| 135 | + let object_progress = OwnShared::new(Mutable::new(object_progress)); | |
| 134 | 136 | |
| 135 | 137 | let start = std::time::Instant::now(); |
| 136 | 138 | in_parallel_with_slice( |
| 137 | 139 | &mut self.root_items, |
| 138 | 140 | thread_limit, |
| 139 | 141 | { |
| 140 | − let object_progress = object_progress.clone(); | |
| 141 | 142 | let child_items = ItemSliceSend(std::ptr::slice_from_raw_parts_mut( |
| 142 | 143 | child_items.as_mut_ptr(), |
| 143 | 144 | child_items.len(), |
| 144 | 145 | )); |
| 145 | − move |thread_index| { | |
| 146 | − ( | |
| 147 | − Vec::<u8>::with_capacity(4096), | |
| 148 | − lock(&object_progress) | |
| 149 | − .add_child_with_id(format!("thread {thread_index}"), gix_features::progress::UNKNOWN), | |
| 150 | − new_thread_state(), | |
| 151 | − resolve.clone(), | |
| 152 | − inspect_object.clone(), | |
| 153 | − child_items.clone(), | |
| 154 | − ) | |
| 146 | + { | |
| 147 | + let object_progress = object_progress.clone(); | |
| 148 | + move |thread_index| { | |
| 149 | + let _ = &child_items; | |
| 150 | + resolve::State { | |
| 151 | + bytes_buf: Vec::<u8>::with_capacity(4096), | |
| 152 | + delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 153 | + fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 154 | + progress: threading::lock(&object_progress) | |
| 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 | 165 | resolve::deltas( |
| 160 | 166 | object_counter.clone(), |
| 161 | 167 | size_counter.clone(), |
| 162 | 168 | node, |
| 163 | 169 | state, |
| 170 | + resolve_data, | |
| 164 | 171 | object_hash.len_in_bytes(), |
| 172 | + threads_left, | |
| 165 | 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 | 182 | size_progress.show_throughput(start); |
| 175 | 183 | |
| 176 | 184 | Ok(Outcome { |
| @@ -1,7 +1,8 @@ | ||
| 1 | −use std::sync::atomic::AtomicBool; | |
| 2 | −use std::{cell::RefCell, collections::BTreeMap, sync::atomic::Ordering}; | |
| 1 | +use std::sync::atomic::{AtomicBool, AtomicIsize}; | |
| 2 | +use std::{collections::BTreeMap, sync::atomic::Ordering}; | |
| 3 | 3 | |
| 4 | −use gix_features::{progress::Progress, zlib}; | |
| 4 | +use gix_features::progress::Progress; | |
| 5 | +use gix_features::{threading, zlib}; | |
| 5 | 6 | |
| 6 | 7 | use crate::{ |
| 7 | 8 | cache::delta::{ |
| @@ -11,49 +12,59 @@ use crate::{ · 3 unmodified lines | ||
| 11 | 12 | }, |
| 12 | 13 | Item, |
| 13 | 14 | }, |
| 15 | + data, | |
| 14 | 16 | data::EntryRange, |
| 15 | 17 | }; |
| 16 | 18 | |
| 19 | +pub(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 | 29 | #[allow(clippy::too_many_arguments)] |
| 18 | −pub(crate) fn deltas<T, F, P, MBFN, S, E>( | |
| 30 | +pub(crate) fn deltas<T, F, MBFN, E, R, P>( | |
| 19 | 31 | object_counter: Option<gix_features::progress::StepShared>, |
| 20 | 32 | size_counter: Option<gix_features::progress::StepShared>, |
| 21 | 33 | node: &mut Item<T>, |
| 22 | − (bytes_buf, ref mut progress, state, resolve, modify_base, child_items): &mut ( | |
| 23 | − Vec<u8>, | |
| 24 | − P, | |
| 25 | − S, | |
| 26 | − F, | |
| 27 | − MBFN, | |
| 28 | − ItemSliceSend<Item<T>>, | |
| 29 | − ), | |
| 34 | + State { | |
| 35 | + bytes_buf, | |
| 36 | + delta_bytes, | |
| 37 | + fully_resolved_delta_bytes, | |
| 38 | + progress, | |
| 39 | + resolve, | |
| 40 | + modify_base, | |
| 41 | + child_items, | |
| 42 | + }: &mut State<P, F, MBFN, T>, | |
| 43 | + resolve_data: &R, | |
| 30 | 44 | hash_len: usize, |
| 45 | + threads_left: &AtomicIsize, | |
| 31 | 46 | should_interrupt: &AtomicBool, |
| 32 | 47 | ) -> Result<(), Error> |
| 33 | 48 | where |
| 34 | 49 | T: Send, |
| 35 | − F: for<'r> Fn(EntryRange, &'r mut Vec<u8>) -> Option<()>, | |
| 50 | + R: Send + Sync, | |
| 36 | 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 | 54 | E: std::error::Error + Send + Sync + 'static, |
| 39 | 55 | { |
| 40 | 56 | let mut decompressed_bytes_by_pack_offset = BTreeMap::new(); |
| 41 | − let bytes_buf = RefCell::new(bytes_buf); | |
| 42 | − let decompress_from_resolver = |slice: EntryRange| -> Result<(crate::data::Entry, u64, Vec<u8>), Error> { | |
| 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 { | |
| 57 | + let mut decompress_from_resolver = |slice: EntryRange, out: &mut Vec<u8>| -> Result<(data::Entry, u64), Error> { | |
| 58 | + let bytes = resolve(slice.clone(), resolve_data).ok_or(Error::ResolveFailed { | |
| 46 | 59 | pack_offset: slice.start, |
| 47 | 60 | })?; |
| 48 | − let entry = crate::data::Entry::from_bytes(&bytes_buf, slice.start, hash_len); | |
| 49 | − let compressed = &bytes_buf[entry.header_size()..]; | |
| 61 | + let entry = data::Entry::from_bytes(bytes, slice.start, hash_len); | |
| 62 | + let compressed = &bytes[entry.header_size()..]; | |
| 50 | 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 | 68 | // each node is a base, and its children always start out as deltas which become a base after applying them. |
| 58 | 69 | // These will be pushed onto our stack until all are processed |
| 59 | 70 | let root_level = 0; |
| @@ -61,19 +72,26 @@ where · 1 unmodified line | ||
| 61 | 72 | root_level, |
| 62 | 73 | Node { |
| 63 | 74 | item: node, |
| 64 | − child_items: child_items.0, | |
| 75 | + child_items: child_items.clone(), | |
| 65 | 76 | }, |
| 66 | 77 | )]; |
| 67 | 78 | while let Some((level, mut base)) = nodes.pop() { |
| 79 | + let storage; | |
| 68 | 80 | if should_interrupt.load(Ordering::Relaxed) { |
| 69 | 81 | return Err(Error::Interrupted); |
| 70 | 82 | } |
| 71 | 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 | 86 | } else { |
| 74 | − decompressed_bytes_by_pack_offset | |
| 87 | + if !bytes_buf.is_empty() { | |
| 88 | + *bytes_buf = Vec::new(); | |
| 89 | + } | |
| 90 | + let (a, b, v) = decompressed_bytes_by_pack_offset | |
| 75 | 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 | 97 | // anything done here must be repeated further down for leaf-nodes. |
| @@ -86,8 +104,7 @@ where · 6 unmodified lines | ||
| 86 | 104 | Context { |
| 87 | 105 | entry: &base_entry, |
| 88 | 106 | entry_end, |
| 89 | − decompressed: &base_bytes, | |
| 90 | − state, | |
| 107 | + decompressed: base_bytes, | |
| 91 | 108 | level, |
| 92 | 109 | }, |
| 93 | 110 | ) |
| @@ -99,20 +116,19 @@ where · 5 unmodified lines | ||
| 99 | 116 | } |
| 100 | 117 | |
| 101 | 118 | for mut child in base.into_child_iter() { |
| 102 | − let (mut child_entry, entry_end, delta_bytes) = decompress_from_resolver(child.entry_slice())?; | |
| 103 | − let (base_size, consumed) = crate::data::delta::decode_header_size(&delta_bytes); | |
| 119 | + let (mut child_entry, entry_end) = decompress_from_resolver(child.entry_slice(), delta_bytes)?; | |
| 120 | + let (base_size, consumed) = data::delta::decode_header_size(delta_bytes); | |
| 104 | 121 | let mut header_ofs = consumed; |
| 105 | 122 | assert_eq!( |
| 106 | 123 | base_bytes.len(), |
| 107 | 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 | 128 | header_ofs += consumed; |
| 112 | 129 | |
| 113 | − let mut fully_resolved_delta_bytes = bytes_buf.borrow_mut(); | |
| 114 | − fully_resolved_delta_bytes.resize(result_size as usize, 0); | |
| 115 | − crate::data::delta::apply(&base_bytes, &mut fully_resolved_delta_bytes, &delta_bytes[header_ofs..]); | |
| 130 | + resize_vec(fully_resolved_delta_bytes, result_size as usize); | |
| 131 | + data::delta::apply(base_bytes, fully_resolved_delta_bytes, &delta_bytes[header_ofs..]); | |
| 116 | 132 | |
| 117 | 133 | // FIXME: this actually invalidates the "pack_offset()" computation, which is not obvious to consumers |
| 118 | 134 | // at all |
| @@ -120,7 +136,7 @@ where · 1 unmodified line | ||
| 120 | 136 | if child.has_children() { |
| 121 | 137 | decompressed_bytes_by_pack_offset.insert( |
| 122 | 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 | 141 | nodes.push((level + 1, child)); |
| 126 | 142 | } else { |
| @@ -130,8 +146,7 @@ where · 3 unmodified lines | ||
| 130 | 146 | Context { |
| 131 | 147 | entry: &child_entry, |
| 132 | 148 | entry_end, |
| 133 | − decompressed: &fully_resolved_delta_bytes, | |
| 134 | − state, | |
| 149 | + decompressed: fully_resolved_delta_bytes, | |
| 135 | 150 | level: level + 1, |
| 136 | 151 | }, |
| 137 | 152 | ) |
| @@ -142,25 +157,273 @@ where · 4 unmodified lines | ||
| 142 | 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 | 193 | Ok(()) |
| 148 | 194 | } |
| 149 | 195 | |
| 150 | −fn decompress_all_at_once(b: &[u8], decompressed_len: usize) -> Result<Vec<u8>, Error> { | |
| 151 | − let mut out = Vec::with_capacity(decompressed_len); | |
| 152 | − // SAFETY: | |
| 153 | − // 1. we have reserved `decompressed_len` | |
| 154 | − // 2. zlib is going to write all of `decompressed_len`. On error, none of the the buffer is made available to the user. | |
| 155 | − #[allow(unsafe_code, clippy::uninit_vec)] | |
| 156 | − unsafe { | |
| 157 | − out.set_len(decompressed_len); | |
| 196 | +/// * `initial_threads` is the threads we may spawn, not accounting for our own thread which is still considered used by the parent | |
| 197 | +/// system. Since this thread will take a controlling function, we may spawn one more than that. In threaded mode, we will finish | |
| 198 | +/// all remaining work. | |
| 199 | +#[allow(clippy::too_many_arguments)] | |
| 200 | +pub(crate) fn deltas_mt<T, F, MBFN, E, R, P>( | |
| 201 | + mut threads_to_create: isize, | |
| 202 | + decompressed_bytes_by_pack_offset: BTreeMap<u64, (data::Entry, u64, Vec<u8>)>, | |
| 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> | |
| 214 | +where | |
| 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 | + | |
| 405 | +fn 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 | + | |
| 420 | +fn decompress_all_at_once(b: &[u8], decompressed_len: usize, out: &mut Vec<u8>) -> Result<(), Error> { | |
| 421 | + resize_vec(out, decompressed_len); | |
| 159 | 422 | zlib::Inflate::default() |
| 160 | − .once(b, &mut out) | |
| 423 | + .once(b, out) | |
| 161 | 424 | .map_err(|err| Error::ZlibInflate { |
| 162 | 425 | source: err, |
| 163 | 426 | message: "Failed to decompress entry", |
| 164 | 427 | })?; |
| 165 | − Ok(out) | |
| 428 | + Ok(()) | |
| 166 | 429 | } |
| @@ -4,6 +4,8 @@ pub struct ItemSliceSend<T>(pub *mut [T]) · 3 unmodified lines | ||
| 4 | 4 | where |
| 5 | 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. | |
| 7 | 9 | impl<T> Clone for ItemSliceSend<T> |
| 8 | 10 | where |
| 9 | 11 | T: Send, |
| @@ -18,12 +20,12 @@ where · 8 unmodified lines | ||
| 18 | 20 | unsafe impl<T> Send for ItemSliceSend<T> where T: Send {} |
| 19 | 21 | |
| 20 | 22 | /// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`]. |
| 21 | −pub struct Node<'a, T> { | |
| 23 | +pub struct Node<'a, T: Send> { | |
| 22 | 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 | |
| 26 | −impl<'a, T> Node<'a, T> { | |
| 28 | +impl<'a, T: Send> Node<'a, T> { | |
| 27 | 29 | /// Returns the offset into the pack at which the `Node`s data is located. |
| 28 | 30 | pub fn offset(&self) -> u64 { |
| 29 | 31 | self.item.offset |
| @@ -55,8 +57,8 @@ impl<'a, T> Node<'a, T> { · 25 unmodified lines | ||
| 55 | 57 | // SAFETY: The resulting mutable pointer cannot be yielded by any other node. |
| 56 | 58 | #[allow(unsafe_code)] |
| 57 | 59 | Node { |
| 58 | − item: unsafe { &mut *(children as *mut Item<T>).add(index as usize) }, | |
| 59 | − child_items: children, | |
| 60 | + item: &mut unsafe { &mut *children.0 }[index as usize], | |
| 61 | + child_items: children.clone(), | |
| 60 | 62 | } |
| 61 | 63 | }) |
| 62 | 64 | } |
| @@ -1,6 +1,9 @@ | ||
| 1 | 1 | use std::sync::atomic::AtomicBool; |
| 2 | 2 | |
| 3 | −use gix_features::{parallel, progress::Progress}; | |
| 3 | +use gix_features::{ | |
| 4 | + parallel, | |
| 5 | + progress::{Progress, RawProgress}, | |
| 6 | +}; | |
| 4 | 7 | |
| 5 | 8 | use crate::index; |
| 6 | 9 | |
| @@ -79,7 +82,7 @@ impl index::File { · 72 unmodified lines | ||
| 79 | 82 | pack: &crate::data::File, |
| 80 | 83 | progress: P, |
| 81 | 84 | should_interrupt: &AtomicBool, |
| 82 | − new_processor: impl Fn() -> Processor + Send + Clone, | |
| 85 | + processor: Processor, | |
| 83 | 86 | Options { |
| 84 | 87 | traversal, |
| 85 | 88 | thread_limit, |
| @@ -91,17 +94,12 @@ impl index::File { · 5 unmodified lines | ||
| 91 | 94 | P: Progress, |
| 92 | 95 | C: crate::cache::DecodeEntry, |
| 93 | 96 | E: std::error::Error + Send + Sync + 'static, |
| 94 | − Processor: FnMut( | |
| 95 | − gix_object::Kind, | |
| 96 | − &[u8], | |
| 97 | − &index::Entry, | |
| 98 | − &mut <P::SubProgress as Progress>::SubProgress, | |
| 99 | − ) -> Result<(), E>, | |
| 97 | + Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn RawProgress) -> Result<(), E> + Send + Clone, | |
| 100 | 98 | F: Fn() -> C + Send + Clone, |
| 101 | 99 | { |
| 102 | 100 | match traversal { |
| 103 | 101 | Algorithm::Lookup => self.traverse_with_lookup( |
| 104 | − new_processor, | |
| 102 | + processor, | |
| 105 | 103 | pack, |
| 106 | 104 | progress, |
| 107 | 105 | should_interrupt, |
| @@ -113,10 +111,10 @@ impl index::File { · 5 unmodified lines | ||
| 113 | 111 | ), |
| 114 | 112 | Algorithm::DeltaTreeLookup => self.traverse_with_index( |
| 115 | 113 | pack, |
| 116 | − new_processor, | |
| 114 | + processor, | |
| 117 | 115 | progress, |
| 118 | 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 | 151 | #[allow(clippy::too_many_arguments)] |
| 154 | − fn decode_and_process_entry<C, P, E>( | |
| 152 | + fn decode_and_process_entry<C, E>( | |
| 155 | 153 | &self, |
| 156 | 154 | check: SafetyCheck, |
| 157 | 155 | pack: &crate::data::File, |
| 158 | 156 | cache: &mut C, |
| 159 | 157 | buf: &mut Vec<u8>, |
| 160 | − progress: &mut P, | |
| 161 | − index_entry: &crate::index::Entry, | |
| 162 | − processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &mut P) -> Result<(), E>, | |
| 158 | + progress: &mut dyn RawProgress, | |
| 159 | + index_entry: &index::Entry, | |
| 160 | + processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn RawProgress) -> Result<(), E>, | |
| 163 | 161 | ) -> Result<crate::data::decode::entry::Outcome, Error<E>> |
| 164 | 162 | where |
| 165 | 163 | C: crate::cache::DecodeEntry, |
| 166 | − P: Progress, | |
| 167 | 164 | E: std::error::Error + Send + Sync + 'static, |
| 168 | 165 | { |
| 169 | 166 | let pack_entry = pack.entry(index_entry.pack_offset); |
| @@ -192,9 +189,9 @@ impl index::File { · 22 unmodified lines | ||
| 192 | 189 | check, |
| 193 | 190 | object_kind, |
| 194 | 191 | buf, |
| 195 | − progress, | |
| 196 | 192 | index_entry, |
| 197 | 193 | || pack.entry_crc32(index_entry.pack_offset, entry_len), |
| 194 | + progress, | |
| 198 | 195 | processor, |
| 199 | 196 | )?; |
| 200 | 197 | Ok(entry_stats) |
| @@ -202,17 +199,16 @@ impl index::File { · 1 unmodified line | ||
| 202 | 199 | } |
| 203 | 200 | |
| 204 | 201 | #[allow(clippy::too_many_arguments)] |
| 205 | −fn process_entry<P, E>( | |
| 202 | +fn process_entry<E>( | |
| 206 | 203 | check: SafetyCheck, |
| 207 | 204 | object_kind: gix_object::Kind, |
| 208 | 205 | decompressed: &[u8], |
| 209 | − progress: &mut P, | |
| 210 | − index_entry: &crate::index::Entry, | |
| 206 | + index_entry: &index::Entry, | |
| 211 | 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 | 210 | ) -> Result<(), Error<E>> |
| 214 | 211 | where |
| 215 | − P: Progress, | |
| 216 | 212 | E: std::error::Error + Send + Sync + 'static, |
| 217 | 213 | { |
| 218 | 214 | if check.object_checksum() { |
| @@ -59,19 +59,16 @@ impl index::File { · 58 unmodified lines | ||
| 59 | 59 | pub fn traverse_with_index<P, Processor, E>( |
| 60 | 60 | &self, |
| 61 | 61 | pack: &crate::data::File, |
| 62 | − new_processor: impl Fn() -> Processor + Send + Clone, | |
| 62 | + mut processor: Processor, | |
| 63 | 63 | mut progress: P, |
| 64 | 64 | should_interrupt: &AtomicBool, |
| 65 | 65 | Options { check, thread_limit }: Options, |
| 66 | 66 | ) -> Result<Outcome<P>, Error<E>> |
| 67 | 67 | where |
| 68 | 68 | P: Progress, |
| 69 | − Processor: FnMut( | |
| 70 | − gix_object::Kind, | |
| 71 | − &[u8], | |
| 72 | − &index::Entry, | |
| 73 | − &mut <P::SubProgress as Progress>::SubProgress, | |
| 74 | − ) -> Result<(), E>, | |
| 69 | + Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn gix_features::progress::RawProgress) -> Result<(), E> | |
| 70 | + + Send | |
| 71 | + + Clone, | |
| 75 | 72 | E: std::error::Error + Send + Sync + 'static, |
| 76 | 73 | { |
| 77 | 74 | let (verify_result, traversal_result) = parallel::join( |
| @@ -113,18 +110,17 @@ impl index::File { · 35 unmodified lines | ||
| 113 | 110 | self.object_hash, |
| 114 | 111 | )?; |
| 115 | 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 | 115 | pack.pack_end() as u64, |
| 118 | − new_processor, | |
| 119 | − |data, | |
| 120 | − progress, | |
| 121 | − traverse::Context { | |
| 122 | − entry: pack_entry, | |
| 123 | − entry_end, | |
| 124 | − decompressed: bytes, | |
| 125 | − state: ref mut processor, | |
| 126 | − level, | |
| 127 | − }| { | |
| 116 | + move |data, | |
| 117 | + progress, | |
| 118 | + traverse::Context { | |
| 119 | + entry: pack_entry, | |
| 120 | + entry_end, | |
| 121 | + decompressed: bytes, | |
| 122 | + level, | |
| 123 | + }| { | |
| 128 | 124 | let object_kind = pack_entry.header.as_kind().expect("non-delta object"); |
| 129 | 125 | data.level = level; |
| 130 | 126 | data.decompressed_size = pack_entry.decompressed_size; |
| @@ -135,7 +131,6 @@ impl index::File { · 4 unmodified lines | ||
| 135 | 131 | check, |
| 136 | 132 | object_kind, |
| 137 | 133 | bytes, |
| 138 | − progress, | |
| 139 | 134 | &data.index_entry, |
| 140 | 135 | || { |
| 141 | 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 | 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 | 147 | match result { |
| 152 | 148 | Err(err @ Error::PackDecode { .. }) if !check.fatal_decode_error() => { |
| @@ -67,7 +67,7 @@ impl index::File { · 66 unmodified lines | ||
| 67 | 67 | /// For more details, see the documentation on the [`traverse()`][index::File::traverse()] method. |
| 68 | 68 | pub fn traverse_with_lookup<P, C, Processor, E, F>( |
| 69 | 69 | &self, |
| 70 | − new_processor: impl Fn() -> Processor + Send + Clone, | |
| 70 | + mut processor: Processor, | |
| 71 | 71 | pack: &data::File, |
| 72 | 72 | mut progress: P, |
| 73 | 73 | should_interrupt: &AtomicBool, |
| @@ -81,12 +81,9 @@ impl index::File { · 7 unmodified lines | ||
| 81 | 81 | P: Progress, |
| 82 | 82 | C: crate::cache::DecodeEntry, |
| 83 | 83 | E: std::error::Error + Send + Sync + 'static, |
| 84 | − Processor: FnMut( | |
| 85 | − gix_object::Kind, | |
| 86 | − &[u8], | |
| 87 | − &index::Entry, | |
| 88 | − &mut <P::SubProgress as Progress>::SubProgress, | |
| 89 | − ) -> Result<(), E>, | |
| 84 | + Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn gix_features::progress::RawProgress) -> Result<(), E> | |
| 85 | + + Send | |
| 86 | + + Clone, | |
| 90 | 87 | F: Fn() -> C + Send + Clone, |
| 91 | 88 | { |
| 92 | 89 | let (verify_result, traversal_result) = parallel::join( |
| @@ -133,7 +130,6 @@ impl index::File { · 40 unmodified lines | ||
| 133 | 130 | move |index| { |
| 134 | 131 | ( |
| 135 | 132 | make_pack_lookup_cache(), |
| 136 | − new_processor(), | |
| 137 | 133 | Vec::with_capacity(2048), // decode buffer |
| 138 | 134 | lock(&reduce_progress) |
| 139 | 135 | .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 | 142 | input_chunks, |
| 147 | 143 | thread_limit, |
| 148 | 144 | state_per_thread, |
| 149 | − |entries: &[index::Entry], | |
| 150 | − (cache, ref mut processor, buf, progress)| | |
| 151 | − -> Result<Vec<data::decode::entry::Outcome>, Error<_>> { | |
| 145 | + move |entries: &[index::Entry], | |
| 146 | + (cache, buf, progress)| | |
| 147 | + -> Result<Vec<data::decode::entry::Outcome>, Error<_>> { | |
| 152 | 148 | progress.init( |
| 153 | 149 | Some(entries.len()), |
| 154 | 150 | gix_features::progress::count_with_decimals("objects", 2), |
| @@ -163,7 +159,7 @@ impl index::File { · 8 unmodified lines | ||
| 163 | 159 | buf, |
| 164 | 160 | progress, |
| 165 | 161 | index_entry, |
| 166 | − processor, | |
| 162 | + &mut processor, | |
| 167 | 163 | ); |
| 168 | 164 | progress.inc(); |
| 169 | 165 | let stat = match result { |
| @@ -198,7 +198,7 @@ impl index::File { · 197 unmodified lines | ||
| 198 | 198 | pack, |
| 199 | 199 | progress, |
| 200 | 200 | should_interrupt, |
| 201 | − || { | |
| 201 | + { | |
| 202 | 202 | let mut encode_buf = Vec::with_capacity(2048); |
| 203 | 203 | move |kind, data, index_entry, progress| { |
| 204 | 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 | 233 | #[allow(clippy::too_many_arguments)] |
| 234 | − fn verify_entry<P>( | |
| 234 | + fn verify_entry( | |
| 235 | 235 | verify_mode: Mode, |
| 236 | 236 | encode_buf: &mut Vec<u8>, |
| 237 | 237 | object_kind: gix_object::Kind, |
| 238 | 238 | buf: &[u8], |
| 239 | 239 | index_entry: &index::Entry, |
| 240 | − progress: &mut P, | |
| 241 | − ) -> Result<(), integrity::Error> | |
| 242 | − where | |
| 243 | − P: Progress, | |
| 244 | − { | |
| 240 | + progress: &dyn gix_features::progress::RawProgress, | |
| 241 | + ) -> Result<(), integrity::Error> { | |
| 245 | 242 | if let Mode::HashCrc32Decode | Mode::HashCrc32DecodeEncode = verify_mode { |
| 246 | 243 | use gix_object::Kind::*; |
| 247 | 244 | match object_kind { |
| @@ -260,7 +257,7 @@ impl index::File { · 12 unmodified lines | ||
| 260 | 257 | .expect("writing to a memory buffer never fails"); |
| 261 | 258 | if encode_buf.as_slice() != buf { |
| 262 | 259 | let mut should_return_error = true; |
| 263 | − if let gix_object::Kind::Tree = object_kind { | |
| 260 | + if let Tree = object_kind { | |
| 264 | 261 | if buf.as_bstr().find(b"100664").is_some() || buf.as_bstr().find(b"100640").is_some() { |
| 265 | 262 | progress.info(format!("Tree object {} would be cleaned up during re-serialization, replacing mode '100664|100640' with '100644'", index_entry.oid)); |
| 266 | 263 | should_return_error = false |
| @@ -83,20 +83,22 @@ impl crate::index::File { · 82 unmodified lines | ||
| 83 | 83 | /// It should return `None` if the entry cannot be resolved from the pack that produced the `entries` iterator, causing |
| 84 | 84 | /// the write operation to fail. |
| 85 | 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 | 87 | version: crate::index::Version, |
| 88 | 88 | make_resolver: F, |
| 89 | 89 | entries: impl Iterator<Item = Result<crate::data::input::Entry, crate::data::input::Error>>, |
| 90 | 90 | thread_limit: Option<usize>, |
| 91 | − mut root_progress: impl Progress, | |
| 91 | + mut root_progress: P, | |
| 92 | 92 | out: impl io::Write, |
| 93 | 93 | should_interrupt: &AtomicBool, |
| 94 | 94 | object_hash: gix_hash::Kind, |
| 95 | 95 | pack_version: crate::data::Version, |
| 96 | 96 | ) -> Result<Outcome, Error> |
| 97 | 97 | where |
| 98 | − F: FnOnce() -> io::Result<F2>, | |
| 99 | − F2: for<'r> Fn(crate::data::EntryRange, &'r mut Vec<u8>) -> Option<()> + Send + Clone, | |
| 98 | + F: FnOnce() -> io::Result<(F2, R)>, | |
| 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 | 103 | if version != crate::index::Version::default() { |
| 102 | 104 | return Err(Error::Unsupported(version)); |
| @@ -180,12 +182,12 @@ impl crate::index::File { · 77 unmodified lines | ||
| 180 | 182 | |
| 181 | 183 | root_progress.inc(); |
| 182 | 184 | |
| 183 | − let resolver = make_resolver()?; | |
| 185 | + let (resolver, pack) = make_resolver()?; | |
| 184 | 186 | let sorted_pack_offsets_by_oid = { |
| 185 | 187 | let traverse::Outcome { roots, children } = tree.traverse( |
| 186 | 188 | resolver, |
| 189 | + &pack, | |
| 187 | 190 | pack_entries_end, |
| 188 | − || (), | |
| 189 | 191 | |data, |
| 190 | 192 | _progress, |
| 191 | 193 | traverse::Context { |
| @@ -250,14 +252,7 @@ impl crate::index::File { · 58 unmodified lines | ||
| 250 | 252 | } |
| 251 | 253 | |
| 252 | 254 | fn 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 | 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 | 257 | entry.id = id; |
| 263 | 258 | } |
| @@ -115,28 +115,25 @@ mod version { · 114 unmodified lines | ||
| 115 | 115 | |
| 116 | 116 | use gix_features::progress; |
| 117 | 117 | use gix_odb::pack; |
| 118 | − use gix_pack::{ | |
| 119 | − data::{input, EntryRange}, | |
| 120 | − index, | |
| 121 | − }; | |
| 118 | + use gix_pack::{data::input, index}; | |
| 122 | 119 | |
| 123 | 120 | use crate::{ |
| 124 | 121 | fixture_path, |
| 125 | 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 | 129 | #[test] |
| 129 | 130 | fn write_to_stream() -> Result<(), Box<dyn std::error::Error>> { |
| 130 | − fn assert_index_write<F>( | |
| 131 | + fn assert_index_write( | |
| 131 | 132 | mode: &input::Mode, |
| 132 | 133 | compressed: &input::EntryDataMode, |
| 133 | 134 | index_path: &&str, |
| 134 | 135 | data_path: &&str, |
| 135 | − resolve: F, | |
| 136 | − ) -> Result<(), Box<dyn std::error::Error>> | |
| 137 | − where | |
| 138 | − F: Fn(pack::data::EntryRange, &mut Vec<u8>) -> Option<()> + Send + Clone, | |
| 139 | − { | |
| 136 | + ) -> Result<(), Box<dyn std::error::Error>> { | |
| 140 | 137 | let pack_iter = pack::data::input::BytesToEntriesIter::new_from_header( |
| 141 | 138 | io::BufReader::new(fs::File::open(fixture_path(data_path))?), |
| 142 | 139 | *mode, |
| @@ -150,7 +147,11 @@ mod version { · 7 unmodified lines | ||
| 150 | 147 | let pack_version = pack_iter.version(); |
| 151 | 148 | let outcome = pack::index::File::write_data_iter_to_stream( |
| 152 | 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 | 155 | pack_iter, |
| 155 | 156 | None, |
| 156 | 157 | progress::Discard, |
| @@ -217,17 +218,7 @@ mod version { · 60 unmodified lines | ||
| 217 | 218 | for mode in &[input::Mode::AsIs, input::Mode::Verify, input::Mode::Restore] { |
| 218 | 219 | for compressed in &[input::EntryDataMode::Crc32, input::EntryDataMode::KeepAndCrc32] { |
| 219 | 220 | for (index_path, data_path) in V2_PACKS_AND_INDICES { |
| 220 | − let resolve = { | |
| 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)?; | |
| 221 | + assert_index_write(mode, compressed, index_path, data_path)?; | |
| 231 | 222 | } |
| 232 | 223 | } |
| 233 | 224 | } |
| @@ -259,11 +250,9 @@ fn traverse_with_index_and_forward_ref_deltas() { · 25 unmodified lines | ||
| 259 | 250 | let _it_should_work = index |
| 260 | 251 | .traverse_with_index( |
| 261 | 252 | &data, |
| 262 | − || { | |
| 263 | − |_, _, _, _| { | |
| 264 | − count.fetch_add(1, Ordering::SeqCst); | |
| 265 | − Ok::<_, std::io::Error>(()) | |
| 266 | − } | |
| 253 | + |_, _, _, _| { | |
| 254 | + count.fetch_add(1, Ordering::SeqCst); | |
| 255 | + Ok::<_, std::io::Error>(()) | |
| 267 | 256 | }, |
| 268 | 257 | progress::Discard, |
| 269 | 258 | &AtomicBool::new(false), |