Skip to main content

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