Skip to main content

velesdb_core/
contiguous_resize.rs

1//! Resize and reallocation logic for [`ContiguousVectors`].
2//!
3//! Extracted from [`super::perf_optimizations`] to isolate the allocation-growth
4//! concern from the core storage API. Uses [`AllocGuard`] for panic-safe buffer
5//! migration during capacity changes.
6//!
7//! [`AllocGuard`]: crate::alloc_guard::AllocGuard
8
9use std::alloc::dealloc;
10use std::ptr::{self, NonNull};
11
12use super::perf_optimizations::ContiguousVectors;
13
14impl ContiguousVectors {
15    /// Ensures the storage has capacity for at least `required_capacity` vectors.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`Error::AllocationFailed`] if reallocation fails.
20    ///
21    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
22    pub fn ensure_capacity(&mut self, required_capacity: usize) -> crate::error::Result<()> {
23        if required_capacity > self.capacity {
24            // #899: `self.capacity * 2` could overflow `usize` and wrap to a tiny
25            // value, defeating the `.max(required_capacity)` guard. Saturate the
26            // doubling so growth always satisfies `required_capacity` and the
27            // final layout-size check (in `resize`) still rejects true overflow.
28            let doubled = self.capacity.saturating_mul(2);
29            let new_capacity = required_capacity.max(doubled);
30            self.resize(new_capacity)?;
31        }
32        Ok(())
33    }
34
35    /// Pre-allocates capacity for `additional` more vectors beyond the current length.
36    ///
37    /// Analogous to [`Vec::reserve`]: ensures the buffer can hold
38    /// `self.len() + additional` vectors without reallocating. No-op if
39    /// sufficient capacity already exists.
40    ///
41    /// Call before a batch push to guarantee `push_batch` won't resize.
42    ///
43    /// # Errors
44    ///
45    /// Returns [`Error::AllocationFailed`] if reallocation fails.
46    ///
47    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
48    pub fn reserve_additional(&mut self, additional: usize) -> crate::error::Result<()> {
49        let required = self.count.saturating_add(additional);
50        self.ensure_capacity(required)
51    }
52
53    /// Resizes the internal buffer.
54    ///
55    /// # P2 Audit + PERF-002: Panic-Safety with RAII Guard
56    ///
57    /// This function uses `AllocGuard` for panic-safe allocation:
58    /// 1. New buffer is allocated via RAII guard (auto-freed on panic)
59    /// 2. Data is copied to new buffer
60    /// 3. Guard ownership is transferred (no auto-free)
61    /// 4. Old buffer is deallocated
62    /// 5. State is updated atomically
63    ///
64    /// If panic occurs during copy, the guard ensures new buffer is freed.
65    pub(crate) fn resize(&mut self, new_capacity: usize) -> crate::error::Result<()> {
66        if new_capacity <= self.capacity {
67            return Ok(());
68        }
69
70        let old_layout = Self::layout(self.dimension, self.capacity)?;
71        let new_layout = Self::layout(self.dimension, new_capacity)?;
72
73        let new_data = Self::alloc_and_copy(new_layout, self.data, self.count, self.dimension)?;
74
75        // Deallocate old buffer
76        // SAFETY: self.data was allocated with old_layout, is non-null (NonNull invariant)
77        // - Condition 1: old_layout matches the allocation parameters.
78        // - Condition 2: Pointer is non-null per NonNull invariant.
79        // SAFETY: Free old buffer after data migration to new buffer.
80        unsafe {
81            dealloc(self.data.as_ptr().cast::<u8>(), old_layout);
82        }
83
84        // Update state (all-or-nothing)
85        self.data = new_data;
86        self.capacity = new_capacity;
87        Ok(())
88    }
89
90    /// Allocates a new buffer and copies existing data into it.
91    ///
92    /// Uses `AllocGuard` for panic-safety: if copy panics, the guard drops
93    /// and frees the new buffer automatically.
94    #[allow(clippy::cast_ptr_alignment)] // Layout is 64-byte aligned
95    fn alloc_and_copy(
96        new_layout: std::alloc::Layout,
97        src: NonNull<f32>,
98        count: usize,
99        dimension: usize,
100    ) -> crate::error::Result<NonNull<f32>> {
101        use crate::alloc_guard::AllocGuard;
102
103        // Allocate zero-initialized buffer with RAII guard (PERF-002)
104        let guard = AllocGuard::new_zeroed(new_layout).ok_or_else(|| {
105            crate::error::Error::AllocationFailed(format!(
106                "Failed to allocate {} bytes for ContiguousVectors resize",
107                new_layout.size()
108            ))
109        })?;
110
111        // EPIC-032/US-002: Use NonNull for type-level guarantee
112        let new_data = NonNull::new(guard.cast::<f32>()).ok_or_else(|| {
113            crate::error::Error::AllocationFailed("AllocGuard returned null pointer".to_string())
114        })?;
115
116        // Copy existing data to new buffer
117        if count > 0 {
118            let copy_size = count * dimension;
119            // SAFETY: Both pointers are valid (NonNull), non-overlapping, and properly aligned
120            // - Condition 1: Source pointer (src) is valid and properly aligned.
121            // - Condition 2: Destination pointer (new_data) is valid and properly aligned.
122            // - Condition 3: Pointers are non-overlapping (old and new allocations are distinct).
123            // SAFETY: Migrate data to newly allocated buffer during resize.
124            unsafe {
125                ptr::copy_nonoverlapping(src.as_ptr(), new_data.as_ptr(), copy_size);
126            }
127        }
128
129        // Transfer ownership - guard won't free on drop anymore
130        let _ = guard.into_raw();
131
132        Ok(new_data)
133    }
134}