Skip to main content

git_internal/internal/pack/
waitlist.rs

1//! Temporary storage for delta objects while their base object is still decoding, keyed by both pack
2//! offset and object hash.
3
4use dashmap::DashMap;
5
6use crate::{hash::ObjectHash, internal::pack::cache_object::CacheObject};
7
8/// Waitlist for Delta objects while the Base object is not ready.
9/// Easier and faster than Channels.
10#[derive(Default, Debug)]
11pub struct Waitlist {
12    //TODO Memory Control!
13    pub map_offset: DashMap<usize, Vec<CacheObject>>,
14    pub map_ref: DashMap<ObjectHash, Vec<CacheObject>>,
15}
16
17impl Waitlist {
18    /// Create a new, empty Waitlist.
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Insert an object into the waitlist by its pack offset or object hash.
24    pub fn insert_offset(&self, offset: usize, obj: CacheObject) {
25        self.map_offset.entry(offset).or_default().push(obj);
26    }
27
28    /// Insert an object into the waitlist by its object hash.
29    pub fn insert_ref(&self, hash: ObjectHash, obj: CacheObject) {
30        self.map_ref.entry(hash).or_default().push(obj);
31    }
32
33    /// Take objects out (get & remove)
34    /// <br> Return Vec::new() if None
35    pub fn take(&self, offset: usize, hash: ObjectHash) -> Vec<CacheObject> {
36        let mut res = Vec::new();
37        if let Some((_, vec)) = self.map_offset.remove(&offset) {
38            res.extend(vec);
39        }
40        if let Some((_, vec)) = self.map_ref.remove(&hash) {
41            res.extend(vec);
42        }
43        res
44    }
45
46    pub fn has_waiters(&self, offset: usize, hash: ObjectHash) -> bool {
47        self.map_offset.contains_key(&offset) || self.map_ref.contains_key(&hash)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::internal::{object::types::ObjectType, pack::cache_object::CacheObjectInfo};
55
56    /// Helper to build a base CacheObject with given size and hash.
57    fn make_test_obj(offset: usize) -> CacheObject {
58        CacheObject {
59            info: CacheObjectInfo::BaseObject(ObjectType::Blob, ObjectHash::default()),
60            offset,
61            crc32: 0,
62            data_decompressed: vec![],
63            mem_recorder: None,
64            is_delta_in_pack: false,
65            known_hash: None,
66        }
67    }
68
69    /// Test inserting and taking objects by offset.
70    #[test]
71    fn test_waitlist_offset() {
72        let waitlist = Waitlist::new();
73        let obj1 = make_test_obj(10);
74        let obj2 = make_test_obj(20);
75
76        waitlist.insert_offset(100, obj1);
77        waitlist.insert_offset(100, obj2);
78
79        let res = waitlist.take(100, ObjectHash::default());
80        assert_eq!(res.len(), 2);
81        assert_eq!(res[0].offset, 10);
82        assert_eq!(res[1].offset, 20);
83
84        let res_empty = waitlist.take(100, ObjectHash::default());
85        assert!(res_empty.is_empty());
86    }
87
88    /// Test inserting and taking objects by object hash.
89    #[test]
90    fn test_waitlist_ref() {
91        let waitlist = Waitlist::new();
92        let hash = ObjectHash::new(b"test_hash");
93        let obj = make_test_obj(30);
94
95        waitlist.insert_ref(hash, obj);
96
97        let res = waitlist.take(0, hash);
98        assert_eq!(res.len(), 1);
99        assert_eq!(res[0].offset, 30);
100
101        let res_empty = waitlist.take(0, hash);
102        assert!(res_empty.is_empty());
103    }
104
105    /// Test inserting and taking objects by both offset and object hash.
106    #[test]
107    fn test_waitlist_mixed() {
108        let waitlist = Waitlist::new();
109        let hash = ObjectHash::new(b"test_hash");
110        let offset = 200;
111
112        let obj1 = make_test_obj(1);
113        let obj2 = make_test_obj(2);
114
115        waitlist.insert_offset(offset, obj1);
116        waitlist.insert_ref(hash, obj2);
117
118        // Take using both keys, should retrieve both lists
119        let res = waitlist.take(offset, hash);
120        assert_eq!(res.len(), 2);
121
122        // Verify we got both objects
123        assert!(res.iter().any(|o| o.offset == 1));
124        assert!(res.iter().any(|o| o.offset == 2));
125
126        // Verify maps are empty
127        assert!(waitlist.map_offset.is_empty());
128        assert!(waitlist.map_ref.is_empty());
129    }
130}