Skip to main content

uqa_core/memory/
vec.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Fallible vectors charge both buffers while moving between allocations.
8
9use super::{
10    buffer_bytes, reconcile_buffer_capacity, replacement, MemoryBudget, MemoryError,
11    MemoryReservation,
12};
13
14#[derive(Debug)]
15pub struct BudgetedVec<T> {
16    values: Vec<T>,
17    memory: MemoryReservation,
18}
19
20impl<T> BudgetedVec<T> {
21    pub fn new(budget: &MemoryBudget) -> Self {
22        Self {
23            values: Vec::new(),
24            memory: budget.empty_reservation(),
25        }
26    }
27
28    pub fn capacity(&self) -> usize {
29        self.values.capacity()
30    }
31
32    pub fn budget(&self) -> &MemoryBudget {
33        self.memory.budget()
34    }
35
36    pub fn reserve(&mut self, additional: usize) -> Result<(), MemoryError> {
37        let required = self
38            .values
39            .len()
40            .checked_add(additional)
41            .ok_or(MemoryError::SizeOverflow)?;
42        if required <= self.values.capacity() {
43            return Ok(());
44        }
45        let (capacity, memory) = replacement::<T>(self.memory.budget(), self.capacity(), required)?;
46        let mut buffer = Self {
47            values: Vec::new(),
48            memory,
49        };
50        buffer.values.try_reserve_exact(capacity)?;
51        self.replace_buffer(buffer)
52    }
53
54    fn replace_buffer(&mut self, mut buffer: Self) -> Result<(), MemoryError> {
55        reconcile_buffer_capacity::<T>(&mut buffer.memory, buffer.values.capacity())?;
56        buffer.values.append(&mut self.values);
57        // Field order frees the old buffer before releasing its reservation.
58        *self = buffer;
59        Ok(())
60    }
61
62    pub fn push(&mut self, value: T) -> Result<(), MemoryError> {
63        self.reserve(1)?;
64        self.values.push(value);
65        Ok(())
66    }
67
68    pub fn clear(&mut self) {
69        self.values.clear();
70    }
71
72    pub fn truncate(&mut self, len: usize) {
73        self.values.truncate(len);
74    }
75
76    /// Request a buffer for the current length, charging the reported capacity of both buffers until the move finishes. A failed reservation or allocation preserves the original values, buffer and reservation.
77    pub fn shrink_to_fit(&mut self) -> Result<(), MemoryError> {
78        let capacity = self.values.len();
79        if capacity == self.values.capacity() {
80            return Ok(());
81        }
82        let memory = self.memory.budget().reserve(buffer_bytes::<T>(capacity)?)?;
83        let mut buffer = Self {
84            values: Vec::new(),
85            memory,
86        };
87        buffer.values.try_reserve_exact(capacity)?;
88        self.replace_buffer(buffer)
89    }
90
91    pub fn pop(&mut self) -> Option<T> {
92        self.values.pop()
93    }
94
95    /// Transfer the buffer and its reservation together; element allocations have separate owners.
96    pub fn into_parts(self) -> (Vec<T>, MemoryReservation) {
97        (self.values, self.memory)
98    }
99}
100
101impl<T> std::ops::Deref for BudgetedVec<T> {
102    type Target = [T];
103
104    fn deref(&self) -> &[T] {
105        &self.values
106    }
107}
108
109impl<T: Copy> BudgetedVec<T> {
110    /// Reserve the complete destination before copying a slice into this buffer.
111    pub fn extend_from_slice(&mut self, values: &[T]) -> Result<(), MemoryError> {
112        self.reserve(values.len())?;
113        self.values.extend_from_slice(values);
114        Ok(())
115    }
116}
117
118impl<T> std::ops::DerefMut for BudgetedVec<T> {
119    fn deref_mut(&mut self) -> &mut [T] {
120        &mut self.values
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn extending_a_slice_reserves_before_mutating_and_preserves_failed_inputs() {
130        let budget = MemoryBudget::new(16);
131        let mut values = BudgetedVec::new(&budget);
132        values.extend_from_slice(&[1_u8, 2, 3, 4]).unwrap();
133        let retained = budget.used();
134        assert!(matches!(
135            values.extend_from_slice(&[9; 32]),
136            Err(MemoryError::Limit { .. })
137        ));
138        assert_eq!(&*values, &[1, 2, 3, 4]);
139        assert_eq!(budget.used(), retained);
140        values.extend_from_slice(&[5, 6]).unwrap();
141        assert_eq!(&*values, &[1, 2, 3, 4, 5, 6]);
142        assert_eq!(budget.used(), values.capacity());
143        drop(values);
144        assert_eq!(budget.used(), 0);
145    }
146
147    #[test]
148    fn shrinking_releases_excess_capacity_after_charging_both_buffers() {
149        let budget = MemoryBudget::new(32);
150        let mut values = BudgetedVec::new(&budget);
151        values
152            .extend_from_slice(&[1_u8, 2, 3, 4, 5, 6, 7, 8])
153            .unwrap();
154        values.truncate(2);
155        values.shrink_to_fit().unwrap();
156        assert_eq!(&*values, &[1, 2]);
157        assert_eq!(values.capacity(), 2);
158        assert_eq!(budget.used(), 2);
159        assert_eq!(budget.peak(), 10);
160        values.shrink_to_fit().unwrap();
161        assert_eq!(budget.used(), 2);
162    }
163
164    #[test]
165    fn failed_shrinking_preserves_the_buffer_and_empty_shrinking_needs_no_headroom() {
166        let budget = MemoryBudget::new(8);
167        let mut values = BudgetedVec::new(&budget);
168        values
169            .extend_from_slice(&[1_u8, 2, 3, 4, 5, 6, 7, 8])
170            .unwrap();
171        values.truncate(2);
172        assert!(matches!(
173            values.shrink_to_fit(),
174            Err(MemoryError::Limit { .. })
175        ));
176        assert_eq!(&*values, &[1, 2]);
177        assert_eq!(values.capacity(), 8);
178        assert_eq!(budget.used(), 8);
179        values.clear();
180        values.shrink_to_fit().unwrap();
181        assert_eq!(values.capacity(), 0);
182        assert_eq!(budget.used(), 0);
183    }
184
185    #[test]
186    fn oversized_replacement_is_charged_before_moving_the_original_values() {
187        for reject in [false, true] {
188            let mut original = vec![11_u32, 22];
189            original.reserve_exact(6);
190            let replacement = Vec::with_capacity(12);
191            let original_capacity = original.capacity();
192            let original_bytes = original_capacity * size_of::<u32>();
193            let replacement_bytes = replacement.capacity() * size_of::<u32>();
194            let budget =
195                MemoryBudget::new(original_bytes + replacement_bytes - usize::from(reject));
196            let mut values = BudgetedVec {
197                values: original,
198                memory: budget.reserve(original_bytes).unwrap(),
199            };
200            // Model an allocator reporting more capacity than the requested length.
201            let buffer = BudgetedVec {
202                values: replacement,
203                memory: budget.reserve(values.len() * size_of::<u32>()).unwrap(),
204            };
205            let result = values.replace_buffer(buffer);
206            assert_eq!(&*values, &[11, 22]);
207            if reject {
208                assert!(matches!(result, Err(MemoryError::Limit { .. })));
209                assert_eq!(values.capacity(), original_capacity);
210                assert_eq!(budget.used(), original_bytes);
211            } else {
212                result.unwrap();
213                assert_eq!(budget.used(), replacement_bytes);
214                assert_eq!(budget.peak(), original_bytes + replacement_bytes);
215            }
216            drop(values);
217            assert_eq!(budget.used(), 0);
218        }
219    }
220
221    #[test]
222    fn shrinking_moves_owned_values_without_copying_or_dropping_survivors() {
223        use std::sync::{
224            atomic::{AtomicUsize, Ordering},
225            Arc,
226        };
227        struct Item(Arc<AtomicUsize>);
228        impl Drop for Item {
229            fn drop(&mut self) {
230                self.0.fetch_add(1, Ordering::Relaxed);
231            }
232        }
233        let budget = MemoryBudget::new(1024);
234        let dropped = Arc::new(AtomicUsize::new(0));
235        let mut values = BudgetedVec::new(&budget);
236        for _ in 0..4 {
237            values.push(Item(Arc::clone(&dropped))).unwrap();
238        }
239        values.truncate(1);
240        assert_eq!(dropped.load(Ordering::Relaxed), 3);
241        values.shrink_to_fit().unwrap();
242        assert_eq!(dropped.load(Ordering::Relaxed), 3);
243        drop(values);
244        assert_eq!(dropped.load(Ordering::Relaxed), 4);
245        assert_eq!(budget.used(), 0);
246    }
247}