Skip to main content

grafeo_common/memory/buffer/
grant.rs

1//! Memory grant RAII wrapper for automatic resource release.
2
3use super::region::MemoryRegion;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7/// Trait for releasing memory grants.
8///
9/// This allows the MemoryGrant to release memory without directly
10/// depending on the full BufferManager type.
11pub trait GrantReleaser: Send + Sync {
12    /// Releases memory back to the manager.
13    fn release(&self, size: usize, region: MemoryRegion);
14
15    /// Tries to allocate additional memory (for resize operations).
16    fn try_allocate_raw(&self, size: usize, region: MemoryRegion) -> bool;
17}
18
19/// RAII wrapper for memory allocations.
20///
21/// Automatically releases memory back to the `BufferManager` when dropped.
22/// Use `consume()` to transfer ownership without releasing.
23pub struct MemoryGrant {
24    /// Reference to the releaser (BufferManager).
25    releaser: Arc<dyn GrantReleaser>,
26    /// Size of this grant in bytes.
27    size: AtomicUsize,
28    /// Memory region for this grant.
29    region: MemoryRegion,
30    /// Whether this grant has been consumed (transferred).
31    consumed: bool,
32}
33
34impl MemoryGrant {
35    /// Creates a new memory grant.
36    pub(crate) fn new(releaser: Arc<dyn GrantReleaser>, size: usize, region: MemoryRegion) -> Self {
37        Self {
38            releaser,
39            size: AtomicUsize::new(size),
40            region,
41            consumed: false,
42        }
43    }
44
45    /// Returns the size of this grant in bytes.
46    #[must_use]
47    pub fn size(&self) -> usize {
48        self.size.load(Ordering::Relaxed)
49    }
50
51    /// Returns the memory region of this grant.
52    #[must_use]
53    pub fn region(&self) -> MemoryRegion {
54        self.region
55    }
56
57    /// Attempts to resize the grant.
58    ///
59    /// Returns `true` if the resize succeeded, `false` if more memory
60    /// could not be allocated.
61    pub fn resize(&mut self, new_size: usize) -> bool {
62        let current = self.size.load(Ordering::Relaxed);
63
64        match new_size.cmp(&current) {
65            std::cmp::Ordering::Greater => {
66                // Need more memory - try to allocate the difference
67                let diff = new_size - current;
68                if self.releaser.try_allocate_raw(diff, self.region) {
69                    self.size.store(new_size, Ordering::Relaxed);
70                    true
71                } else {
72                    false
73                }
74            }
75            std::cmp::Ordering::Less => {
76                // Releasing memory
77                let diff = current - new_size;
78                self.releaser.release(diff, self.region);
79                self.size.store(new_size, Ordering::Relaxed);
80                true
81            }
82            std::cmp::Ordering::Equal => true,
83        }
84    }
85
86    /// Splits off a portion of this grant into a new grant.
87    ///
88    /// Returns `None` if the requested amount exceeds the current size.
89    pub fn split(&mut self, amount: usize) -> Option<MemoryGrant> {
90        let current = self.size.load(Ordering::Relaxed);
91        if amount > current {
92            return None;
93        }
94
95        self.size.store(current - amount, Ordering::Relaxed);
96        Some(MemoryGrant {
97            releaser: Arc::clone(&self.releaser),
98            size: AtomicUsize::new(amount),
99            region: self.region,
100            consumed: false,
101        })
102    }
103
104    /// Merges another grant into this one.
105    ///
106    /// Both grants must be for the same region.
107    ///
108    /// # Panics
109    ///
110    /// Panics if the grants are for different regions.
111    pub fn merge(&mut self, other: MemoryGrant) {
112        assert_eq!(
113            self.region, other.region,
114            "Cannot merge grants from different regions"
115        );
116
117        let other_size = other.consume();
118        let current = self.size.load(Ordering::Relaxed);
119        self.size.store(current + other_size, Ordering::Relaxed);
120    }
121
122    /// Consumes this grant without releasing memory.
123    ///
124    /// Use this to transfer ownership of the memory to another owner.
125    /// The caller is responsible for eventually releasing the memory.
126    pub fn consume(mut self) -> usize {
127        self.consumed = true;
128        self.size.load(Ordering::Relaxed)
129    }
130
131    /// Returns whether this grant has been consumed.
132    #[must_use]
133    pub fn is_consumed(&self) -> bool {
134        self.consumed
135    }
136
137    /// Returns whether this grant is empty (size == 0).
138    #[must_use]
139    pub fn is_empty(&self) -> bool {
140        self.size.load(Ordering::Relaxed) == 0
141    }
142}
143
144impl Drop for MemoryGrant {
145    fn drop(&mut self) {
146        if !self.consumed {
147            let size = self.size.load(Ordering::Relaxed);
148            if size > 0 {
149                self.releaser.release(size, self.region);
150            }
151        }
152    }
153}
154
155impl std::fmt::Debug for MemoryGrant {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        f.debug_struct("MemoryGrant")
158            .field("size", &self.size.load(Ordering::Relaxed))
159            .field("region", &self.region)
160            .field("consumed", &self.consumed)
161            .finish()
162    }
163}
164
165/// A collection of memory grants that can be managed together.
166#[derive(Default)]
167pub struct CompositeGrant {
168    grants: Vec<MemoryGrant>,
169}
170
171impl CompositeGrant {
172    /// Creates a new empty composite grant.
173    #[must_use]
174    pub fn new() -> Self {
175        Self { grants: Vec::new() }
176    }
177
178    /// Adds a grant to the collection.
179    pub fn add(&mut self, grant: MemoryGrant) {
180        self.grants.push(grant);
181    }
182
183    /// Returns the total size of all grants.
184    #[must_use]
185    pub fn total_size(&self) -> usize {
186        self.grants.iter().map(MemoryGrant::size).sum()
187    }
188
189    /// Returns the number of grants.
190    #[must_use]
191    pub fn len(&self) -> usize {
192        self.grants.len()
193    }
194
195    /// Returns whether the collection is empty.
196    #[must_use]
197    pub fn is_empty(&self) -> bool {
198        self.grants.is_empty()
199    }
200
201    /// Consumes all grants and returns the total size.
202    pub fn consume_all(self) -> usize {
203        self.grants.into_iter().map(MemoryGrant::consume).sum()
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::sync::atomic::AtomicUsize;
211
212    struct MockReleaser {
213        released: AtomicUsize,
214        allocated: AtomicUsize,
215    }
216
217    impl MockReleaser {
218        fn new() -> Arc<Self> {
219            Arc::new(Self {
220                released: AtomicUsize::new(0),
221                allocated: AtomicUsize::new(0),
222            })
223        }
224    }
225
226    impl GrantReleaser for MockReleaser {
227        fn release(&self, size: usize, _region: MemoryRegion) {
228            self.released.fetch_add(size, Ordering::Relaxed);
229        }
230
231        fn try_allocate_raw(&self, size: usize, _region: MemoryRegion) -> bool {
232            self.allocated.fetch_add(size, Ordering::Relaxed);
233            true
234        }
235    }
236
237    #[test]
238    fn test_grant_drop_releases_memory() {
239        let releaser = MockReleaser::new();
240
241        {
242            let _grant = MemoryGrant::new(
243                Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
244                1024,
245                MemoryRegion::ExecutionBuffers,
246            );
247            assert_eq!(releaser.released.load(Ordering::Relaxed), 0);
248        }
249
250        // After drop, memory should be released
251        assert_eq!(releaser.released.load(Ordering::Relaxed), 1024);
252    }
253
254    #[test]
255    fn test_grant_consume_no_release() {
256        let releaser = MockReleaser::new();
257
258        let grant = MemoryGrant::new(
259            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
260            1024,
261            MemoryRegion::ExecutionBuffers,
262        );
263
264        let size = grant.consume();
265        assert_eq!(size, 1024);
266
267        // No release should happen
268        assert_eq!(releaser.released.load(Ordering::Relaxed), 0);
269    }
270
271    #[test]
272    fn test_grant_resize_grow() {
273        let releaser = MockReleaser::new();
274
275        let mut grant = MemoryGrant::new(
276            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
277            1024,
278            MemoryRegion::ExecutionBuffers,
279        );
280
281        assert!(grant.resize(2048));
282        assert_eq!(grant.size(), 2048);
283        assert_eq!(releaser.allocated.load(Ordering::Relaxed), 1024);
284    }
285
286    #[test]
287    fn test_grant_resize_shrink() {
288        let releaser = MockReleaser::new();
289
290        let mut grant = MemoryGrant::new(
291            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
292            1024,
293            MemoryRegion::ExecutionBuffers,
294        );
295
296        assert!(grant.resize(512));
297        assert_eq!(grant.size(), 512);
298        assert_eq!(releaser.released.load(Ordering::Relaxed), 512);
299    }
300
301    #[test]
302    fn test_grant_split() {
303        let releaser = MockReleaser::new();
304
305        let mut grant = MemoryGrant::new(
306            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
307            1000,
308            MemoryRegion::ExecutionBuffers,
309        );
310
311        let split = grant.split(400).unwrap();
312        assert_eq!(grant.size(), 600);
313        assert_eq!(split.size(), 400);
314
315        // Cannot split more than available
316        assert!(grant.split(1000).is_none());
317    }
318
319    #[test]
320    fn test_grant_merge() {
321        let releaser = MockReleaser::new();
322
323        let mut grant1 = MemoryGrant::new(
324            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
325            600,
326            MemoryRegion::ExecutionBuffers,
327        );
328
329        let grant2 = MemoryGrant::new(
330            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
331            400,
332            MemoryRegion::ExecutionBuffers,
333        );
334
335        grant1.merge(grant2);
336        assert_eq!(grant1.size(), 1000);
337
338        // grant2 was consumed during merge, no release
339        assert_eq!(releaser.released.load(Ordering::Relaxed), 0);
340    }
341
342    #[test]
343    fn test_composite_grant() {
344        let releaser = MockReleaser::new();
345
346        let mut composite = CompositeGrant::new();
347        assert!(composite.is_empty());
348
349        composite.add(MemoryGrant::new(
350            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
351            100,
352            MemoryRegion::ExecutionBuffers,
353        ));
354        composite.add(MemoryGrant::new(
355            Arc::clone(&releaser) as Arc<dyn GrantReleaser>,
356            200,
357            MemoryRegion::ExecutionBuffers,
358        ));
359
360        assert_eq!(composite.len(), 2);
361        assert_eq!(composite.total_size(), 300);
362
363        let total = composite.consume_all();
364        assert_eq!(total, 300);
365
366        // No release since all were consumed
367        assert_eq!(releaser.released.load(Ordering::Relaxed), 0);
368    }
369}