History

computing hashes for bases from decompressed in-memory store works

Sebastian Thiel committed 6 years ago

Commit 7c19fe61 parent a95ce9c3 files · +135 −62Browse files

Unified · Split

3 files
  1. git-odb/src/pack/index/write/mod.rs +64 −17
  2. git-odb/src/pack/index/write/types.rs +31 −7
  3. git-odb/tests/pack/index.rs +40 −38
git-odb/src/pack/index/write/mod.rs+64−17
@@ -1,7 +1,7 @@
1use crate::{hash, pack, pack::index::util::Chunks, pack::index::V2_SIGNATURE};1use crate::{hash, loose, pack, pack::index::util::Chunks, pack::index::V2_SIGNATURE};
2use byteorder::{BigEndian, WriteBytesExt};2use byteorder::{BigEndian, WriteBytesExt};
3use git_features::{parallel, parallel::in_parallel_if, progress::Progress};3use git_features::{parallel, parallel::in_parallel_if, progress::Progress};
4use git_object::owned;4use git_object::{owned, HashKind};
5use smallvec::alloc::collections::BTreeMap;5use smallvec::alloc::collections::BTreeMap;
6use std::io;6use std::io;
7 7
@@ -28,7 +28,6 @@ impl pack::index::File { · 20 unmodified lines
28 {28 {
29 use io::Write;29 use io::Write;
30 30
31 let mut out = hash::Write::new(out, kind.hash());
32 if kind != pack::index::Kind::default() {31 if kind != pack::index::Kind::default() {
33 return Err(Error::Unsupported(kind));32 return Err(Error::Unsupported(kind));
34 }33 }
@@ -81,7 +80,7 @@ impl pack::index::File { · 46 unmodified lines
81 .ok_or_else(|| {80 .ok_or_else(|| {
82 Error::IteratorInvariantBasesBeforeDeltasNeedThem(pack_offset, base_pack_offset)81 Error::IteratorInvariantBasesBeforeDeltasNeedThem(pack_offset, base_pack_offset)
83 })?82 })?
84 .child_count += 1;83 .increment_child_count();
85 (84 (
86 mode.delta_cache(compressed, decompressed),85 mode.delta_cache(compressed, decompressed),
87 ObjectKind::OfsDelta(base_pack_offset),86 ObjectKind::OfsDelta(base_pack_offset),
@@ -89,13 +88,7 @@ impl pack::index::File { · 1 unmodified line
89 }88 }
90 };89 };
91 90
92 cache_by_offset.insert(91 cache_by_offset.insert(pack_offset, CacheEntry::new(cache));
93 pack_offset,
94 CacheEntry {
95 child_count: 0,
96 _cache: cache,
97 },
98 );
99 index_entries.push(Entry {92 index_entries.push(Entry {
100 pack_offset,93 pack_offset,
101 kind,94 kind,
@@ -121,7 +114,14 @@ impl pack::index::File { · 19 unmodified lines
121 thread_limit,114 thread_limit,
122 |_| (),115 |_| (),
123 |base_pack_offsets, state| {116 |base_pack_offsets, state| {
124 apply_deltas(base_pack_offsets, state, &index_entries, &_cache_by_offset, &mode)117 apply_deltas(
118 base_pack_offsets,
119 state,
120 &index_entries,
121 &_cache_by_offset,
122 &mode,
123 kind.hash(),
124 )
125 },125 },
126 Reducer::new(num_objects),126 Reducer::new(num_objects),
127 )?;127 )?;
@@ -139,15 +139,16 @@ impl pack::index::File { · 11 unmodified lines
139 drop(index_entries);139 drop(index_entries);
140 140
141 // Write header141 // Write header
142 let mut out = hash::Write::new(out, kind.hash());
142 out.write_all(V2_SIGNATURE)?;143 out.write_all(V2_SIGNATURE)?;
143 out.write_u32::<BigEndian>(kind as u32)?;144 out.write_u32::<BigEndian>(kind as u32)?;
144 145
145 // todo: write fanout146 // todo: write fanout
146 147
147 let _index_hash = out.hash.digest();148 let index_hash = out.hash.digest().into();
148 Ok(Outcome {149 Ok(Outcome {
149 index_kind: kind,150 index_kind: kind,
150 index_hash: owned::Id::from([0u8; 20]),151 index_hash,
151 pack_hash: last_seen_trailer.ok_or(Error::IteratorInvariantTrailer)?,152 pack_hash: last_seen_trailer.ok_or(Error::IteratorInvariantTrailer)?,
152 num_objects,153 num_objects,
153 })154 })
@@ -155,14 +156,60 @@ impl pack::index::File { · 1 unmodified line
155}156}
156 157
157fn apply_deltas<F>(158fn apply_deltas<F>(
158 _base_entries: Vec<&Entry>,159 base_entries: Vec<&Entry>,
159 _state: &mut (),160 _state: &mut (),
160 _entries: &[Entry],161 _entries: &[Entry],
161 _caches: &parking_lot::Mutex<BTreeMap<u64, CacheEntry>>,162 caches: &parking_lot::Mutex<BTreeMap<u64, CacheEntry>>,
162 _mode: &Mode<F>,163 _mode: &Mode<F>,
164 hash_kind: HashKind,
163) -> Vec<(u64, owned::Id)>165) -> Vec<(u64, owned::Id)>
164where166where
165 F: for<'r> Fn(u64, &'r mut Vec<u8>) -> Option<(pack::data::Header, u64)> + Send + Sync,167 F: for<'r> Fn(u64, &'r mut Vec<u8>) -> Option<(pack::data::Header, u64)> + Send + Sync,
166{168{
167 Vec::new()169 let decompressed_bytes_from_cache = |pack_offset: &u64| -> (bool, Vec<u8>) {
170 let cache = caches
171 .lock()
172 .get_mut(pack_offset)
173 .expect("an entry for every pack offset")
174 .cache();
175 let (is_borrowed, cache) = match cache {
176 Bytes::Borrowed(b) => (true, b),
177 Bytes::Owned(b) => (false, b),
178 };
179 let bytes = match cache {
180 Cache::Decompressed(b) => b,
181 Cache::Compressed(_, _) => unimplemented!("decompress once"),
182 Cache::Unset => unimplemented!("use resolver"),
183 };
184 (is_borrowed, bytes)
185 };
186 let possibly_return_to_cache = |pack_offset: &u64, is_borrowed: bool, bytes: Vec<u8>| {
187 if is_borrowed {
188 caches
189 .lock()
190 .get_mut(pack_offset)
191 .expect("an entry for every pack offset")
192 .set_decompressed(bytes);
193 }
194 };
195 let compute_hash = |kind: git_object::Kind, bytes: &[u8]| -> owned::Id {
196 let mut write = hash::Write::new(io::sink(), hash_kind);
197 loose::object::header::encode(kind, bytes.len() as u64, &mut write)
198 .expect("write to sink and hash cannot fail");
199 write.hash.update(bytes);
200 owned::Id::from(write.hash.digest())
201 };
202 let mut out = Vec::with_capacity(base_entries.len()); // perfectly conservative guess
203
204 for Entry { pack_offset, kind, .. } in base_entries {
205 let (is_borrowed, base_bytes) = decompressed_bytes_from_cache(pack_offset);
206 out.push((
207 *pack_offset,
208 compute_hash(kind.to_kind().expect("base object"), &base_bytes),
209 ));
210 possibly_return_to_cache(pack_offset, is_borrowed, base_bytes);
211 }
212
213 out.shrink_to_fit();
214 out
168}215}
git-odb/src/pack/index/write/types.rs+31−7
@@ -30,6 +30,12 @@ impl ObjectKind { · 29 unmodified lines
30 ObjectKind::OfsDelta(_) => false,30 ObjectKind::OfsDelta(_) => false,
31 }31 }
32 }32 }
33 pub fn to_kind(&self) -> Option<git_object::Kind> {
34 match self {
35 ObjectKind::Base(kind) => Some(*kind),
36 ObjectKind::OfsDelta(_) => None,
37 }
38 }
33}39}
34 40
35pub(crate) struct Entry {41pub(crate) struct Entry {
@@ -39,26 +45,44 @@ pub(crate) struct Entry { · 3 unmodified lines
39}45}
40 46
41pub(crate) struct CacheEntry {47pub(crate) struct CacheEntry {
42 pub _cache: Cache,48 cache: Cache,
43 /// When it reaches zero, the cache can be freed49 /// When it reaches zero, the cache can be freed
44 pub child_count: u32,50 child_count: u32,
45}51}
46 52
47pub(crate) enum _Bytes {53pub(crate) enum Bytes {
48 Owned(Cache),54 Owned(Cache),
49 Borrowed(Cache),55 Borrowed(Cache),
50}56}
51 57
58/// Note that every operation in the CacheEntry must be fast, as these happen behind a lock
52impl CacheEntry {59impl CacheEntry {
53 pub fn _decr(&mut self) -> _Bytes {60 pub fn new(cache: Cache) -> Self {
61 CacheEntry {
62 child_count: 0,
63 cache: cache,
64 }
65 }
66 pub fn increment_child_count(&mut self) {
67 self.child_count += 1;
68 }
69 pub fn _decr(&mut self) -> Bytes {
54 self.child_count -= 1;70 self.child_count -= 1;
55 let cache = std::mem::replace(&mut self._cache, Cache::Unset);71 self.cache()
72 }
73
74 pub fn cache(&mut self) -> Bytes {
75 let cache = std::mem::replace(&mut self.cache, Cache::Unset);
56 if self.child_count == 0 {76 if self.child_count == 0 {
57 _Bytes::Owned(cache)77 Bytes::Owned(cache)
58 } else {78 } else {
59 _Bytes::Borrowed(cache)79 Bytes::Borrowed(cache)
60 }80 }
61 }81 }
82 pub fn set_decompressed(&mut self, bytes: Vec<u8>) {
83 assert_ne!(self.child_count, 0, "Do not return decompressed bytes once nobody is interested in the data anymore, i.e. from `Bytes::Owned(…)`");
84 self.cache = Cache::Decompressed(bytes);
85 }
62}86}
63 87
64/// The function resolves pack_offset: u64 into compressed bytes to &mut Vec<u8> and returns (object kind, decompressed size)88/// The function resolves pack_offset: u64 into compressed bytes to &mut Vec<u8> and returns (object kind, decompressed size)
git-odb/tests/pack/index.rs+40−38
@@ -77,46 +77,48 @@ mod method { · 76 unmodified lines
77 fn write_to_stream() -> Result<(), Box<dyn std::error::Error>> {77 fn write_to_stream() -> Result<(), Box<dyn std::error::Error>> {
78 for mode in &[Mode::AsIs, Mode::Verify, Mode::Restore] {78 for mode in &[Mode::AsIs, Mode::Verify, Mode::Restore] {
79 for (index_path, data_path) in V2_PACKS_AND_INDICES {79 for (index_path, data_path) in V2_PACKS_AND_INDICES {
80 let pack_iter = pack::data::Iter::new_from_header(80 for memory_mode in vec![pack::index::write::Mode::in_memory_decompressed()] {
81 io::BufReader::new(fs::File::open(fixture_path(data_path))?),81 let pack_iter = pack::data::Iter::new_from_header(
82 *mode,82 io::BufReader::new(fs::File::open(fixture_path(data_path))?),
83 )?;83 *mode,
84 )?;
84 85
85 let mut actual = Vec::<u8>::new();86 let mut actual = Vec::<u8>::new();
86 let desired_kind = pack::index::Kind::default();87 let desired_kind = pack::index::Kind::default();
87 let num_objects = pack_iter.len() as u32;88 let num_objects = pack_iter.len() as u32;
88 let outcome = pack::index::File::write_data_iter_to_stream(89 let outcome = pack::index::File::write_data_iter_to_stream(
89 desired_kind,90 desired_kind,
90 pack::index::write::Mode::in_memory_decompressed(),91 memory_mode,
91 pack_iter,92 pack_iter,
92 None,93 None,
93 progress::Discard,94 progress::Discard,
94 &mut actual,95 &mut actual,
95 )?;96 )?;
96 97
97 let expected = fs::read(fixture_path(index_path))?;98 let expected = fs::read(fixture_path(index_path))?;
98 let end_of_header = 4 * 2;99 let end_of_header = 4 * 2;
99 assert_eq!(actual, &expected[..end_of_header], "we should get the header right");100 assert_eq!(actual, &expected[..end_of_header], "we should get the header right");
100 // let end_of_fanout_table = end_of_header + 256 * 4;101 // let end_of_fanout_table = end_of_header + 256 * 4;
101 // assert_eq!(102 // assert_eq!(
102 // actual,103 // actual,
103 // &expected[..end_of_fanout_table],104 // &expected[..end_of_fanout_table],
104 // "we should get the fanout table right"105 // "we should get the fanout table right"
105 // );106 // );
106 // TODO: comment this in for the final test - keep the above anyway though, useful if something breaks107 // TODO: comment this in for the final test - keep the above anyway though, useful if something breaks
107 // assert_eq!(108 // assert_eq!(
108 // actual, expected,109 // actual, expected,
109 // "we should be writing a bit-exact version of the original V2 index"110 // "we should be writing a bit-exact version of the original V2 index"
110 // );111 // );
111 assert_eq!(112 assert_eq!(
112 outcome.num_objects, num_objects,113 outcome.num_objects, num_objects,
113 "it wrote the entire iterator worth of entries"114 "it wrote the entire iterator worth of entries"
114 );115 );
115 assert_eq!(outcome.index_kind, desired_kind);116 assert_eq!(outcome.index_kind, desired_kind);
116 assert_eq!(117 assert_eq!(
117 outcome.index_hash,118 outcome.index_hash,
118 pack::index::File::at(fixture_path(index_path))?.index_checksum()119 pack::index::File::at(fixture_path(index_path))?.index_checksum()
119 );120 );
121 }
120 }122 }
121 }123 }
122 Ok(())124 Ok(())

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t