git_odb/store_impls/dynamic/load_one.rs
1use std::{
2 path::Path,
3 sync::{atomic::Ordering, Arc},
4};
5
6use crate::store::{handle, types};
7
8impl super::Store {
9 /// If Ok(None) is returned, the pack-id was stale and referred to an unloaded pack or a pack which couldn't be
10 /// loaded as its file didn't exist on disk anymore.
11 /// If the oid is known, just load indices again to continue
12 /// (objects rarely ever removed so should be present, maybe in another pack though),
13 /// and redo the entire lookup for a valid pack id whose pack can probably be loaded next time.
14 pub(crate) fn load_pack(
15 &self,
16 id: types::PackId,
17 marker: types::SlotIndexMarker,
18 ) -> std::io::Result<Option<Arc<git_pack::data::File>>> {
19 let index = self.index.load();
20 if index.generation != marker.generation {
21 return Ok(None);
22 }
23 fn load_pack(
24 path: &Path,
25 id: types::PackId,
26 object_hash: git_hash::Kind,
27 ) -> std::io::Result<Arc<git_pack::data::File>> {
28 git_pack::data::File::at(path, object_hash)
29 .map(|mut pack| {
30 pack.id = id.to_intrinsic_pack_id();
31 Arc::new(pack)
32 })
33 .map_err(|err| match err {
34 git_pack::data::header::decode::Error::Io { source, .. } => source,
35 other => std::io::Error::new(std::io::ErrorKind::Other, other),
36 })
37 }
38
39 let slot = &self.files[id.index];
40 // pin the current state before loading in the generation. That way we won't risk seeing the wrong value later.
41 let slot_files = &**slot.files.load();
42 if slot.generation.load(Ordering::SeqCst) > marker.generation {
43 // There is a disk consolidation in progress which just overwrote a slot that could be disposed with some other
44 // pack, one we didn't intend to load.
45 // Hope that when the caller returns/retries the new index is set so they can fetch it and retry.
46 return Ok(None);
47 }
48 match id.multipack_index {
49 None => {
50 match slot_files {
51 Some(types::IndexAndPacks::Index(bundle)) => {
52 match bundle.data.loaded() {
53 Some(pack) => Ok(Some(pack.clone())),
54 None => {
55 let _lock = slot.write.lock();
56 let mut files = slot.files.load_full();
57 let files_mut = Arc::make_mut(&mut files);
58 let pack = match files_mut {
59 Some(types::IndexAndPacks::Index(bundle)) => bundle
60 .data
61 .load_with_recovery(|path| load_pack(path, id, self.object_hash))?,
62 Some(types::IndexAndPacks::MultiIndex(_)) => {
63 // something changed between us getting the lock, trigger a complete index refresh.
64 None
65 }
66 None => {
67 unreachable!("BUG: must set this handle to be stable to avoid slots to be cleared/changed")
68 }
69 };
70 slot.files.store(files);
71 Ok(pack)
72 }
73 }
74 }
75 // This can also happen if they use an old index into our new and refreshed data which might have a multi-index
76 // here.
77 Some(types::IndexAndPacks::MultiIndex(_)) => Ok(None),
78 None => {
79 unreachable!("BUG: must set this handle to be stable to avoid slots to be cleared/changed")
80 }
81 }
82 }
83 Some(pack_index) => {
84 match slot_files {
85 Some(types::IndexAndPacks::MultiIndex(bundle)) => {
86 match bundle.data.get(pack_index as usize) {
87 None => Ok(None), // somewhat unexpected, data must be stale
88 Some(on_disk_pack) => match on_disk_pack.loaded() {
89 Some(pack) => Ok(Some(pack.clone())),
90 None => {
91 let _lock = slot.write.lock();
92 let mut files = slot.files.load_full();
93 let files_mut = Arc::make_mut(&mut files);
94 let pack = match files_mut {
95 Some(types::IndexAndPacks::Index(_)) => {
96 // something changed between us getting the lock, trigger a complete index refresh.
97 None
98 }
99 Some(types::IndexAndPacks::MultiIndex(bundle)) => bundle
100 .data
101 .get_mut(pack_index as usize)
102 .expect("BUG: must set this handle to be stable")
103 .load_with_recovery(|path| load_pack(path, id, self.object_hash))?,
104 None => {
105 unreachable!("BUG: must set this handle to be stable to avoid slots to be cleared/changed")
106 }
107 };
108 slot.files.store(files);
109 Ok(pack)
110 }
111 },
112 }
113 }
114 // This can also happen if they use an old index into our new and refreshed data which might have a multi-index
115 // here.
116 Some(types::IndexAndPacks::Index(_)) => Ok(None),
117 None => {
118 unreachable!("BUG: must set this handle to be stable to avoid slots to be cleared/changed")
119 }
120 }
121 }
122 }
123 }
124
125 /// Similar to `.load_pack()`, but for entire indices, bypassing the index entirely and going solely by marker and id.
126 /// Returns `None` if the index wasn't available anymore or could otherwise not be loaded, which can be considered a bug
127 /// as we should always keep needed indices available.
128 pub(crate) fn index_by_id(&self, id: types::PackId, marker: types::SlotIndexMarker) -> Option<handle::IndexLookup> {
129 let slot = self.files.get(id.index)?;
130 // Pin this value before we check the generation to avoid seeing something newer later.
131 let slot_files = &**slot.files.load();
132 if slot.generation.load(Ordering::SeqCst) > marker.generation {
133 // This means somebody just overwrote our trashed slot with a new (or about to be stored) index, which means the slot isn't
134 // what we need it to be.
135 // This shouldn't as we won't overwrite slots while handles need stable indices.
136 return None;
137 }
138 let lookup = match (slot_files).as_ref()? {
139 types::IndexAndPacks::Index(bundle) => handle::SingleOrMultiIndex::Single {
140 index: bundle.index.loaded()?.clone(),
141 data: bundle.data.loaded().cloned(),
142 },
143 types::IndexAndPacks::MultiIndex(multi) => handle::SingleOrMultiIndex::Multi {
144 index: multi.multi_index.loaded()?.clone(),
145 data: multi.data.iter().map(|f| f.loaded().cloned()).collect(),
146 },
147 };
148 handle::IndexLookup {
149 id: id.index,
150 file: lookup,
151 }
152 .into()
153 }
154}