gix_odb/store_impls/dynamic/
handle.rs

1use std::{
2    cell::RefCell,
3    ops::Deref,
4    rc::Rc,
5    sync::{atomic::Ordering, Arc},
6};
7
8use gix_features::threading::OwnShared;
9use gix_hash::oid;
10
11use crate::store::{handle, types, RefreshMode};
12
13pub(crate) enum SingleOrMultiIndex {
14    Single {
15        index: Arc<gix_pack::index::File>,
16        data: Option<Arc<gix_pack::data::File>>,
17    },
18    Multi {
19        index: Arc<gix_pack::multi_index::File>,
20        data: Vec<Option<Arc<gix_pack::data::File>>>,
21    },
22}
23
24/// A utility to allow looking up pack offsets for a particular pack
25pub(crate) enum IntraPackLookup<'a> {
26    Single(&'a gix_pack::index::File),
27    /// the internal pack-id inside of a multi-index for which the lookup is supposed to be.
28    /// Used to prevent ref-delta OIDs to, for some reason, point to a different pack.
29    Multi {
30        index: &'a gix_pack::multi_index::File,
31        required_pack_index: gix_pack::multi_index::PackIndex,
32    },
33}
34
35impl IntraPackLookup<'_> {
36    pub(crate) fn pack_offset_by_id(&self, id: &oid) -> Option<gix_pack::data::Offset> {
37        match self {
38            IntraPackLookup::Single(index) => index
39                .lookup(id)
40                .map(|entry_index| index.pack_offset_at_index(entry_index)),
41            IntraPackLookup::Multi {
42                index,
43                required_pack_index,
44            } => index.lookup(id).and_then(|entry_index| {
45                let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(entry_index);
46                (pack_index == *required_pack_index).then_some(pack_offset)
47            }),
48        }
49    }
50}
51
52pub struct IndexLookup {
53    pub(crate) file: SingleOrMultiIndex,
54    /// The index we were found at in the slot map
55    pub(crate) id: types::IndexId,
56}
57
58pub struct IndexForObjectInPack {
59    /// The internal identifier of the pack itself, which either is referred to by an index or a multi-pack index.
60    pub(crate) pack_id: types::PackId,
61    /// The offset at which the object's entry can be found
62    pub(crate) pack_offset: u64,
63}
64
65pub(crate) mod index_lookup {
66    use std::{collections::HashSet, sync::Arc};
67
68    use gix_hash::oid;
69
70    use crate::store::{handle, handle::IntraPackLookup, types};
71
72    pub(crate) struct Outcome<'a> {
73        pub object_index: handle::IndexForObjectInPack,
74        pub index_file: IntraPackLookup<'a>,
75        pub pack: &'a mut Option<Arc<gix_pack::data::File>>,
76    }
77
78    impl handle::IndexLookup {
79        /// Return an iterator over the entries of the given pack. The `pack_id` is required to identify a pack uniquely within
80        /// a potential multi-pack index.
81        pub(crate) fn iter(
82            &self,
83            pack_id: types::PackId,
84        ) -> Option<Box<dyn Iterator<Item = gix_pack::index::Entry> + '_>> {
85            (self.id == pack_id.index).then(|| match &self.file {
86                handle::SingleOrMultiIndex::Single { index, .. } => index.iter(),
87                handle::SingleOrMultiIndex::Multi { index, .. } => {
88                    let pack_index = pack_id.multipack_index.expect(
89                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",
90                    );
91                    Box::new(index.iter().filter_map(move |e| {
92                        (e.pack_index == pack_index).then_some(gix_pack::index::Entry {
93                            oid: e.oid,
94                            pack_offset: e.pack_offset,
95                            crc32: None,
96                        })
97                    }))
98                }
99            })
100        }
101
102        pub(crate) fn pack(&mut self, pack_id: types::PackId) -> Option<&'_ mut Option<Arc<gix_pack::data::File>>> {
103            (self.id == pack_id.index).then(move || match &mut self.file {
104                handle::SingleOrMultiIndex::Single { data, .. } => data,
105                handle::SingleOrMultiIndex::Multi { data, .. } => {
106                    let pack_index = pack_id.multipack_index.expect(
107                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",
108                    );
109                    &mut data[pack_index as usize]
110                }
111            })
112        }
113
114        /// Return true if the given object id exists in this index
115        pub(crate) fn contains(&self, object_id: &oid) -> bool {
116            match &self.file {
117                handle::SingleOrMultiIndex::Single { index, .. } => index.lookup(object_id).is_some(),
118                handle::SingleOrMultiIndex::Multi { index, .. } => index.lookup(object_id).is_some(),
119            }
120        }
121
122        /// Return true if the given object id exists in this index
123        pub(crate) fn oid_at_index(&self, entry_index: u32) -> &gix_hash::oid {
124            match &self.file {
125                handle::SingleOrMultiIndex::Single { index, .. } => index.oid_at_index(entry_index),
126                handle::SingleOrMultiIndex::Multi { index, .. } => index.oid_at_index(entry_index),
127            }
128        }
129
130        /// Return the amount of objects contained in the index, essentially the number of object ids.
131        pub(crate) fn num_objects(&self) -> u32 {
132            match &self.file {
133                handle::SingleOrMultiIndex::Single { index, .. } => index.num_objects(),
134                handle::SingleOrMultiIndex::Multi { index, .. } => index.num_objects(),
135            }
136        }
137
138        /// Call `lookup_prefix(…)` on either index or multi-index, and transform matches into an object id.
139        pub(crate) fn lookup_prefix(
140            &self,
141            prefix: gix_hash::Prefix,
142            candidates: Option<&mut HashSet<gix_hash::ObjectId>>,
143        ) -> Option<crate::store::prefix::lookup::Outcome> {
144            let mut candidate_entries = candidates.as_ref().map(|_| 0..0);
145            let res = match &self.file {
146                handle::SingleOrMultiIndex::Single { index, .. } => {
147                    index.lookup_prefix(prefix, candidate_entries.as_mut())
148                }
149                handle::SingleOrMultiIndex::Multi { index, .. } => {
150                    index.lookup_prefix(prefix, candidate_entries.as_mut())
151                }
152            }?;
153
154            if let Some((candidates, entries)) = candidates.zip(candidate_entries) {
155                candidates.extend(entries.map(|entry| self.oid_at_index(entry).to_owned()));
156            }
157            Some(res.map(|entry_index| self.oid_at_index(entry_index).to_owned()))
158        }
159
160        /// See if the oid is contained in this index, and return its full id for lookup possibly alongside its data file if already
161        /// loaded.
162        /// Also return the index itself as it's needed to resolve intra-pack ref-delta objects. They are a possibility even though
163        /// they won't be used in practice as it's more efficient to store their offsets.
164        /// If it is not loaded, ask it to be loaded and put it into the returned mutable option for safe-keeping.
165        pub(crate) fn lookup(&mut self, object_id: &oid) -> Option<Outcome<'_>> {
166            let id = self.id;
167            match &mut self.file {
168                handle::SingleOrMultiIndex::Single { index, data } => index.lookup(object_id).map(move |idx| Outcome {
169                    object_index: handle::IndexForObjectInPack {
170                        pack_id: types::PackId {
171                            index: id,
172                            multipack_index: None,
173                        },
174                        pack_offset: index.pack_offset_at_index(idx),
175                    },
176                    index_file: IntraPackLookup::Single(index),
177                    pack: data,
178                }),
179                handle::SingleOrMultiIndex::Multi { index, data } => index.lookup(object_id).map(move |idx| {
180                    let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(idx);
181                    Outcome {
182                        object_index: handle::IndexForObjectInPack {
183                            pack_id: types::PackId {
184                                index: id,
185                                multipack_index: Some(pack_index),
186                            },
187                            pack_offset,
188                        },
189                        index_file: IntraPackLookup::Multi {
190                            index,
191                            required_pack_index: pack_index,
192                        },
193                        pack: &mut data[pack_index as usize],
194                    }
195                }),
196            }
197        }
198    }
199}
200
201pub(crate) enum Mode {
202    DeletedPacksAreInaccessible,
203    /// This mode signals that we should not unload packs even after they went missing.
204    KeepDeletedPacksAvailable,
205}
206
207/// Handle registration
208impl super::Store {
209    pub(crate) fn register_handle(&self) -> Mode {
210        self.num_handles_unstable.fetch_add(1, Ordering::Relaxed);
211        Mode::DeletedPacksAreInaccessible
212    }
213    pub(crate) fn remove_handle(&self, mode: Mode) {
214        match mode {
215            Mode::KeepDeletedPacksAvailable => {
216                let _lock = self.write.lock();
217                self.num_handles_stable.fetch_sub(1, Ordering::SeqCst)
218            }
219            Mode::DeletedPacksAreInaccessible => self.num_handles_unstable.fetch_sub(1, Ordering::Relaxed),
220        };
221    }
222    pub(crate) fn upgrade_handle(&self, mode: Mode) -> Mode {
223        if let Mode::DeletedPacksAreInaccessible = mode {
224            let _lock = self.write.lock();
225            self.num_handles_stable.fetch_add(1, Ordering::SeqCst);
226            self.num_handles_unstable.fetch_sub(1, Ordering::SeqCst);
227        }
228        Mode::KeepDeletedPacksAvailable
229    }
230}
231
232/// Handle creation
233impl super::Store {
234    /// The amount of times a ref-delta base can be followed when multi-indices are involved.
235    pub const INITIAL_MAX_RECURSION_DEPTH: usize = 32;
236
237    /// Create a new cache filled with a handle to this store, if this store is supporting shared ownership.
238    ///
239    /// Note that the actual type of `OwnShared` depends on the `parallel` feature toggle of the `gix-features` crate.
240    pub fn to_cache(self: &OwnShared<Self>) -> crate::Cache<super::Handle<OwnShared<super::Store>>> {
241        self.to_handle().into()
242    }
243
244    /// Create a new cache filled with a handle to this store if this store is held in an `Arc`.
245    pub fn to_cache_arc(self: &Arc<Self>) -> crate::Cache<super::Handle<Arc<super::Store>>> {
246        self.to_handle_arc().into()
247    }
248
249    /// Create a new database handle to this store if this store is supporting shared ownership.
250    ///
251    /// See also, [`to_cache()`][super::Store::to_cache()] which is probably more useful.
252    pub fn to_handle(self: &OwnShared<Self>) -> super::Handle<OwnShared<super::Store>> {
253        let token = self.register_handle();
254        super::Handle {
255            store: self.clone(),
256            refresh: RefreshMode::default(),
257            ignore_replacements: false,
258            token: Some(token),
259            inflate: RefCell::new(Default::default()),
260            snapshot: RefCell::new(self.collect_snapshot()),
261            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,
262            packed_object_count: Default::default(),
263        }
264    }
265
266    /// Create a new database handle to this store if this store is held in an `Arc`.
267    ///
268    /// This method is useful in applications that know they will use threads.
269    pub fn to_handle_arc(self: &Arc<Self>) -> super::Handle<Arc<super::Store>> {
270        let token = self.register_handle();
271        super::Handle {
272            store: self.clone(),
273            refresh: Default::default(),
274            ignore_replacements: false,
275            token: Some(token),
276            inflate: RefCell::new(Default::default()),
277            snapshot: RefCell::new(self.collect_snapshot()),
278            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,
279            packed_object_count: Default::default(),
280        }
281    }
282
283    /// Transform the only instance into an `Arc<Self>` or panic if this is not the only Rc handle
284    /// to the contained store.
285    ///
286    /// This is meant to be used when the `gix_features::threading::OwnShared` refers to an `Rc` as it was compiled without the
287    /// `parallel` feature toggle.
288    pub fn into_shared_arc(self: OwnShared<Self>) -> Arc<Self> {
289        match OwnShared::try_unwrap(self) {
290            Ok(this) => Arc::new(this),
291            Err(_) => panic!("BUG: Must be called when there is only one owner for this RC"),
292        }
293    }
294}
295
296impl<S> super::Handle<S>
297where
298    S: Deref<Target = super::Store> + Clone,
299{
300    /// Call once if pack ids are stored and later used for lookup, meaning they should always remain mapped and not be unloaded
301    /// even if they disappear from disk.
302    /// This must be called if there is a chance that git maintenance is happening while a pack is created.
303    pub fn prevent_pack_unload(&mut self) {
304        self.token = self.token.take().map(|token| self.store.upgrade_handle(token));
305    }
306
307    /// Return a shared reference to the contained store.
308    pub fn store_ref(&self) -> &S::Target {
309        &self.store
310    }
311
312    /// Return an owned store with shared ownership.
313    pub fn store(&self) -> S {
314        self.store.clone()
315    }
316
317    /// Set the handle to never cause ODB refreshes if an object could not be found.
318    ///
319    /// The latter is the default, as typically all objects referenced in a git repository are contained in the local clone.
320    /// More recently, however, this doesn't always have to be the case due to sparse checkouts and other ways to only have a
321    /// limited amount of objects available locally.
322    pub fn refresh_never(&mut self) {
323        self.refresh = RefreshMode::Never;
324    }
325
326    /// Return the current refresh mode.
327    pub fn refresh_mode(&mut self) -> RefreshMode {
328        self.refresh
329    }
330}
331
332impl<S> Drop for super::Handle<S>
333where
334    S: Deref<Target = super::Store> + Clone,
335{
336    fn drop(&mut self) {
337        if let Some(token) = self.token.take() {
338            self.store.remove_handle(token);
339        }
340    }
341}
342
343impl TryFrom<&super::Store> for super::Store {
344    type Error = std::io::Error;
345
346    fn try_from(s: &super::Store) -> Result<Self, Self::Error> {
347        super::Store::at_opts(
348            s.path().into(),
349            &mut s.replacements(),
350            crate::store::init::Options {
351                slots: crate::store::init::Slots::Given(s.files.len().try_into().expect("BUG: too many slots")),
352                object_hash: Default::default(),
353                use_multi_pack_index: false,
354                current_dir: s.current_dir.clone().into(),
355            },
356        )
357    }
358}
359
360impl super::Handle<Rc<super::Store>> {
361    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store.
362    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {
363        let store = Arc::new(super::Store::try_from(self.store_ref())?);
364        let mut cache = store.to_handle_arc();
365        cache.refresh = self.refresh;
366        cache.max_recursion_depth = self.max_recursion_depth;
367        Ok(cache)
368    }
369}
370
371impl super::Handle<Arc<super::Store>> {
372    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store
373    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {
374        Ok(self)
375    }
376}
377
378impl<S> Clone for super::Handle<S>
379where
380    S: Deref<Target = super::Store> + Clone,
381{
382    fn clone(&self) -> Self {
383        super::Handle {
384            store: self.store.clone(),
385            refresh: self.refresh,
386            ignore_replacements: self.ignore_replacements,
387            token: {
388                let token = self.store.register_handle();
389                match self.token.as_ref().expect("token is always set here ") {
390                    handle::Mode::DeletedPacksAreInaccessible => token,
391                    handle::Mode::KeepDeletedPacksAvailable => self.store.upgrade_handle(token),
392                }
393                .into()
394            },
395            inflate: RefCell::new(Default::default()),
396            snapshot: RefCell::new(self.store.collect_snapshot()),
397            max_recursion_depth: self.max_recursion_depth,
398            packed_object_count: Default::default(),
399        }
400    }
401}