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}