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 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] | 5 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] |
| 6 | license = "MIT/Apache-2.0" | 6 | license = "MIT/Apache-2.0" |
| 7 | description = "Implements git packs and related data structures" | 7 | description = "Implements git packs and related data structures" |
| 8 | edition = "2018" | 8 | edition = "2021" |
| 9 | include = ["src/**/*", "CHANGELOG.md"] | 9 | include = ["src/**/*", "CHANGELOG.md"] |
| 10 | rust-version = "1.64" | 10 | rust-version = "1.64" |
| 11 | autotests = false | 11 | autotests = false |
| @@ -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 should | 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 | /// 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 | ||
| 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 | fn new_pack_file_resolver( | 358 | fn 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 | ||
| 373 | struct WriteOutcome { | 370 | struct WriteOutcome { |
| @@ -1,9 +1,10 @@ | |||
| 1 | use std::sync::atomic::{AtomicBool, Ordering}; | 1 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 2 | 2 | ||
| 3 | use gix_features::threading::{Mutable, OwnShared}; | ||
| 3 | use gix_features::{ | 4 | use 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 | ||
| 9 | use crate::{ | 10 | use 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 tree | 37 | /// 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. |
| 42 | pub struct Context<'a, S> { | 45 | pub struct Context<'a> { |
| 43 | /// The pack entry describing the object | 46 | /// 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 indicating | 52 | /// 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 otherwise | 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 | /// 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 function | 93 | /// * `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 | where | 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 | 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 { |
| @@ -1,7 +1,8 @@ | |||
| 1 | use std::sync::atomic::AtomicBool; | 1 | use std::sync::atomic::{AtomicBool, AtomicIsize}; |
| 2 | use std::{cell::RefCell, collections::BTreeMap, sync::atomic::Ordering}; | 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 | use crate::{ | 7 | use 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 | ||
| 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 | #[allow(clippy::too_many_arguments)] | 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 | 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> |
| 33 | where | 48 | where |
| 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 processed | 69 | // 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_offset | 87 | 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 consumers | 133 | // FIXME: this actually invalidates the "pack_offset()" computation, which is not obvious to consumers |
| 118 | // at all | 134 | // 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 | ||
| 150 | fn 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. | 200 | pub(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> | ||
| 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 | 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 | } |
| @@ -4,6 +4,8 @@ pub struct ItemSliceSend<T>(pub *mut [T]) · 3 unmodified lines | |||
| 4 | where | 4 | where |
| 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. | ||
| 7 | impl<T> Clone for ItemSliceSend<T> | 9 | impl<T> Clone for ItemSliceSend<T> |
| 8 | where | 10 | where |
| 9 | T: Send, | 11 | T: Send, |
| @@ -18,12 +20,12 @@ where · 8 unmodified lines | |||
| 18 | unsafe impl<T> Send for ItemSliceSend<T> where T: Send {} | 20 | unsafe 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`]. |
| 21 | pub struct Node<'a, T> { | 23 | pub 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 | ||
| 26 | impl<'a, T> Node<'a, T> { | 28 | impl<'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.offset | 31 | 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 | } |
| @@ -1,6 +1,9 @@ | |||
| 1 | use std::sync::atomic::AtomicBool; | 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 | use crate::index; | 8 | use 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 | where | 162 | 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)] |
| 205 | fn process_entry<P, E>( | 202 | fn 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>> |
| 214 | where | 211 | where |
| 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() { |
| @@ -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 | where | 67 | 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() => { |
| @@ -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 buffer | 133 | 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 progress | 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 | 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 { |
| @@ -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 = false | 263 | should_return_error = false |
| @@ -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, causing | 83 | /// 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 | where | 97 | 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 | ||
| 252 | fn modify_base(entry: &mut TreeEntry, pack_entry: &crate::data::Entry, decompressed: &[u8], hash: gix_hash::Kind) { | 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 | 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 | } |
| @@ -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 = index | 250 | 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), |