git_odb/store_impls/dynamic/
types.rs1use std::{
2 path::{Path, PathBuf},
3 sync::{
4 atomic::{AtomicU16, AtomicU32, AtomicUsize, Ordering},
5 Arc,
6 },
7 time::SystemTime,
8};
9
10use arc_swap::ArcSwap;
11use git_features::hash;
12
13pub type IndexId = usize;
15pub(crate) type StateId = u32;
16pub(crate) type Generation = u32;
17pub(crate) type AtomicGeneration = AtomicU32;
18
19#[derive(Default, Copy, Clone)]
22pub struct SlotIndexMarker {
23 pub(crate) generation: Generation,
27 pub(crate) state_id: StateId,
30}
31
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
34pub struct PackId {
35 pub(crate) index: IndexId,
37 pub(crate) multipack_index: Option<git_pack::multi_index::PackIndex>,
39}
40
41impl PackId {
42 pub(crate) const fn max_indices() -> usize {
44 (1 << 15) - 1
45 }
46 pub(crate) const fn max_packs_in_multi_index() -> git_pack::multi_index::PackIndex {
48 (1 << 16) - 1
49 }
50 pub(crate) fn to_intrinsic_pack_id(self) -> git_pack::data::Id {
56 assert!(self.index < (1 << 15), "There shouldn't be more than 2^15 indices");
57 match self.multipack_index {
58 None => self.index as git_pack::data::Id,
59 Some(midx) => {
60 assert!(
61 midx <= Self::max_packs_in_multi_index(),
62 "There shouldn't be more than 2^16 packs per multi-index"
63 );
64 ((self.index as git_pack::data::Id | 1 << 15) | midx << 16) as git_pack::data::Id
65 }
66 }
67 }
68
69 pub(crate) fn from_intrinsic_pack_id(pack_id: git_pack::data::Id) -> Self {
70 if pack_id & (1 << 15) == 0 {
71 PackId {
72 index: (pack_id & 0x7fff) as IndexId,
73 multipack_index: None,
74 }
75 } else {
76 PackId {
77 index: (pack_id & 0x7fff) as IndexId,
78 multipack_index: Some(pack_id >> 16),
79 }
80 }
81 }
82}
83
84#[derive(Default)]
86pub struct SlotMapIndex {
87 pub(crate) slot_indices: Vec<usize>,
89 pub(crate) loose_dbs: Arc<Vec<crate::loose::Store>>,
93
94 pub(crate) generation: Generation,
96 pub(crate) next_index_to_load: Arc<AtomicUsize>,
100 pub(crate) loaded_indices: Arc<AtomicUsize>,
104 pub(crate) num_indices_currently_being_loaded: Arc<AtomicU16>,
107}
108
109impl SlotMapIndex {
110 pub(crate) fn state_id(self: &Arc<SlotMapIndex>) -> StateId {
111 let hash = hash::crc32(&(Arc::as_ptr(self) as usize).to_be_bytes());
114 hash::crc32_update(hash, &self.loaded_indices.load(Ordering::SeqCst).to_be_bytes())
115 }
116
117 pub(crate) fn marker(self: &Arc<SlotMapIndex>) -> SlotIndexMarker {
118 SlotIndexMarker {
119 generation: self.generation,
120 state_id: self.state_id(),
121 }
122 }
123
124 pub(crate) fn is_initialized(&self) -> bool {
126 !self.loose_dbs.is_empty()
127 }
128}
129
130#[derive(Clone)]
131pub(crate) struct OnDiskFile<T: Clone> {
132 path: Arc<PathBuf>,
134 mtime: SystemTime,
136 state: OnDiskFileState<T>,
137}
138
139#[derive(Clone)]
140pub(crate) enum OnDiskFileState<T: Clone> {
141 Unloaded,
143 Loaded(T),
144 Garbage(T),
148 Missing,
150}
151
152impl<T: Clone> OnDiskFile<T> {
153 pub fn path(&self) -> &Path {
154 &self.path
155 }
156 pub fn is_loaded(&self) -> bool {
158 matches!(self.state, OnDiskFileState::Loaded(_) | OnDiskFileState::Garbage(_))
159 }
160
161 pub fn is_disposable(&self) -> bool {
163 matches!(self.state, OnDiskFileState::Garbage(_) | OnDiskFileState::Missing)
164 }
165
166 pub(crate) fn load_strict(&mut self, load: impl FnOnce(&Path) -> std::io::Result<T>) -> std::io::Result<()> {
168 use OnDiskFileState::*;
169 match self.state {
170 Unloaded | Missing => match load(&self.path) {
171 Ok(v) => {
172 self.state = Loaded(v);
173 Ok(())
174 }
175 Err(err) => {
176 self.state = Missing;
178 Err(err)
179 }
180 },
181 Loaded(_) | Garbage(_) => Ok(()),
182 }
183 }
184 pub fn load_with_recovery(&mut self, load: impl FnOnce(&Path) -> std::io::Result<T>) -> std::io::Result<Option<T>> {
187 use OnDiskFileState::*;
188 match &mut self.state {
189 Loaded(v) | Garbage(v) => Ok(Some(v.clone())),
190 Missing => Ok(None),
191 Unloaded => match load(&self.path) {
192 Ok(v) => {
193 self.state = OnDiskFileState::Loaded(v.clone());
194 Ok(Some(v))
195 }
196 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
197 self.state = OnDiskFileState::Missing;
198 Ok(None)
199 }
200 Err(err) => Err(err),
201 },
202 }
203 }
204
205 pub fn loaded(&self) -> Option<&T> {
206 use OnDiskFileState::*;
207 match &self.state {
208 Loaded(v) | Garbage(v) => Some(v),
209 Unloaded | Missing => None,
210 }
211 }
212
213 pub fn put_back(&mut self) {
214 match std::mem::replace(&mut self.state, OnDiskFileState::Missing) {
215 OnDiskFileState::Garbage(v) => self.state = OnDiskFileState::Loaded(v),
216 OnDiskFileState::Missing => self.state = OnDiskFileState::Unloaded,
217 other @ OnDiskFileState::Loaded(_) | other @ OnDiskFileState::Unloaded => self.state = other,
218 }
219 }
220
221 pub fn trash(&mut self) {
222 match std::mem::replace(&mut self.state, OnDiskFileState::Missing) {
223 OnDiskFileState::Loaded(v) => self.state = OnDiskFileState::Garbage(v),
224 other @ OnDiskFileState::Garbage(_)
225 | other @ OnDiskFileState::Unloaded
226 | other @ OnDiskFileState::Missing => self.state = other,
227 }
228 }
229}
230
231#[derive(Clone)]
232pub(crate) struct IndexFileBundle {
233 pub index: OnDiskFile<Arc<git_pack::index::File>>,
234 pub data: OnDiskFile<Arc<git_pack::data::File>>,
235}
236
237#[derive(Clone)]
238pub(crate) struct MultiIndexFileBundle {
239 pub multi_index: OnDiskFile<Arc<git_pack::multi_index::File>>,
240 pub data: Vec<OnDiskFile<Arc<git_pack::data::File>>>,
241}
242
243#[derive(Clone)]
244pub(crate) enum IndexAndPacks {
245 Index(IndexFileBundle),
246 MultiIndex(MultiIndexFileBundle),
248}
249
250impl IndexAndPacks {
251 pub(crate) fn index_path(&self) -> &Path {
252 match self {
253 IndexAndPacks::Index(index) => &index.index.path,
254 IndexAndPacks::MultiIndex(index) => &index.multi_index.path,
255 }
256 }
257
258 pub(crate) fn mtime(&self) -> SystemTime {
259 match self {
260 IndexAndPacks::Index(index) => index.index.mtime,
261 IndexAndPacks::MultiIndex(index) => index.multi_index.mtime,
262 }
263 }
264
265 pub(crate) fn put_back(&mut self) {
267 match self {
268 IndexAndPacks::Index(bundle) => {
269 bundle.index.put_back();
270 bundle.data.put_back();
271 }
272 IndexAndPacks::MultiIndex(bundle) => {
273 bundle.multi_index.put_back();
274 for data in &mut bundle.data {
275 data.put_back();
276 }
277 }
278 }
279 }
280
281 pub(crate) fn trash(&mut self) {
283 match self {
284 IndexAndPacks::Index(bundle) => {
285 bundle.index.trash();
286 bundle.data.trash();
287 }
288 IndexAndPacks::MultiIndex(bundle) => {
289 bundle.multi_index.trash();
290 for data in &mut bundle.data {
291 data.trash();
292 }
293 }
294 }
295 }
296
297 pub(crate) fn index_is_loaded(&self) -> bool {
298 match self {
299 Self::Index(bundle) => bundle.index.is_loaded(),
300 Self::MultiIndex(bundle) => bundle.multi_index.is_loaded(),
301 }
302 }
303
304 pub(crate) fn is_disposable(&self) -> bool {
305 match self {
306 Self::Index(bundle) => bundle.index.is_disposable() || bundle.data.is_disposable(),
307 Self::MultiIndex(bundle) => {
308 bundle.multi_index.is_disposable() || bundle.data.iter().any(|odf| odf.is_disposable())
309 }
310 }
311 }
312
313 pub(crate) fn load_index(&mut self, object_hash: git_hash::Kind) -> std::io::Result<()> {
314 match self {
315 IndexAndPacks::Index(bundle) => bundle.index.load_strict(|path| {
316 git_pack::index::File::at(path, object_hash)
317 .map(Arc::new)
318 .map_err(|err| match err {
319 git_pack::index::init::Error::Io { source, .. } => source,
320 err => std::io::Error::new(std::io::ErrorKind::Other, err),
321 })
322 }),
323 IndexAndPacks::MultiIndex(bundle) => {
324 bundle.multi_index.load_strict(|path| {
325 git_pack::multi_index::File::at(path)
326 .map(Arc::new)
327 .map_err(|err| match err {
328 git_pack::multi_index::init::Error::Io { source, .. } => source,
329 err => std::io::Error::new(std::io::ErrorKind::Other, err),
330 })
331 })?;
332 if let Some(multi_index) = bundle.multi_index.loaded() {
333 bundle.data = Self::index_names_to_pack_paths(multi_index);
334 }
335 Ok(())
336 }
337 }
338 }
339
340 pub(crate) fn new_single(index_path: PathBuf, mtime: SystemTime) -> Self {
341 let data_path = index_path.with_extension("pack");
342 Self::Index(IndexFileBundle {
343 index: OnDiskFile {
344 path: index_path.into(),
345 state: OnDiskFileState::Unloaded,
346 mtime,
347 },
348 data: OnDiskFile {
349 path: data_path.into(),
350 state: OnDiskFileState::Unloaded,
351 mtime,
352 },
353 })
354 }
355
356 pub(crate) fn new_multi_from_open_file(multi_index: Arc<git_pack::multi_index::File>, mtime: SystemTime) -> Self {
357 let data = Self::index_names_to_pack_paths(&multi_index);
358 Self::MultiIndex(MultiIndexFileBundle {
359 multi_index: OnDiskFile {
360 path: Arc::new(multi_index.path().to_owned()),
361 state: OnDiskFileState::Loaded(multi_index),
362 mtime,
363 },
364 data,
365 })
366 }
367
368 fn index_names_to_pack_paths(
369 multi_index: &git_pack::multi_index::File,
370 ) -> Vec<OnDiskFile<Arc<git_pack::data::File>>> {
371 let parent_dir = multi_index.path().parent().expect("parent present");
372 let data = multi_index
373 .index_names()
374 .iter()
375 .map(|idx| OnDiskFile {
376 path: parent_dir.join(idx.with_extension("pack")).into(),
377 state: OnDiskFileState::Unloaded,
378 mtime: SystemTime::UNIX_EPOCH,
379 })
380 .collect();
381 data
382 }
383}
384
385#[derive(Default)]
386pub(crate) struct MutableIndexAndPack {
387 pub(crate) files: ArcSwap<Option<IndexAndPacks>>,
388 pub(crate) write: parking_lot::Mutex<()>,
389 pub(crate) generation: AtomicGeneration,
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
397#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
398pub struct Metrics {
399 pub num_handles: usize,
401 pub num_refreshes: usize,
403 pub open_reachable_indices: usize,
405 pub known_reachable_indices: usize,
407 pub open_reachable_packs: usize,
409 pub known_packs: usize,
411 pub unused_slots: usize,
415 pub unreachable_indices: usize,
421 pub unreachable_packs: usize,
423 pub loose_dbs: usize,
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 mod pack_id {
434 use super::PackId;
435
436 #[test]
437 fn to_intrinsic_roundtrip() {
438 let single = PackId {
439 index: (1 << 15) - 1,
440 multipack_index: None,
441 };
442 let multi = PackId {
443 index: (1 << 15) - 1,
444 multipack_index: Some((1 << 16) - 1),
445 };
446 assert_eq!(PackId::from_intrinsic_pack_id(single.to_intrinsic_pack_id()), single);
447 assert_eq!(PackId::from_intrinsic_pack_id(multi.to_intrinsic_pack_id()), multi);
448 }
449
450 #[test]
451 #[should_panic]
452 fn max_supported_index_count() {
453 PackId {
454 index: 1 << 15,
455 multipack_index: None,
456 }
457 .to_intrinsic_pack_id();
458 }
459 }
460}