1use crate::error::{Result, WalError};
11
12pub const DEFAULT_ALIGNMENT: usize = 4096;
18
19pub struct AlignedBuf {
24 ptr: *mut u8,
26
27 capacity: usize,
29
30 len: usize,
32
33 alignment: usize,
35
36 layout: std::alloc::Layout,
38}
39
40unsafe impl Send for AlignedBuf {}
43
44impl AlignedBuf {
45 pub fn new(min_capacity: usize, alignment: usize) -> Result<Self> {
50 assert!(
51 alignment.is_power_of_two(),
52 "alignment must be power of two"
53 );
54 assert!(alignment > 0, "alignment must be > 0");
55
56 let capacity = round_up(min_capacity.max(alignment), alignment);
58
59 let layout = std::alloc::Layout::from_size_align(capacity, alignment).map_err(|_| {
60 WalError::AlignmentViolation {
61 context: "buffer allocation",
62 required: alignment,
63 actual: min_capacity,
64 }
65 })?;
66
67 let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
69 if ptr.is_null() {
70 std::alloc::handle_alloc_error(layout);
71 }
72
73 Ok(Self {
74 ptr,
75 capacity,
76 len: 0,
77 alignment,
78 layout,
79 })
80 }
81
82 pub fn with_default_alignment(min_capacity: usize) -> Result<Self> {
84 Self::new(min_capacity, DEFAULT_ALIGNMENT)
85 }
86
87 pub fn write(&mut self, data: &[u8]) -> usize {
92 let available = self.capacity - self.len;
93 let to_write = data.len().min(available);
94 if to_write > 0 {
95 unsafe {
97 std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(self.len), to_write);
98 }
99 self.len += to_write;
100 }
101 to_write
102 }
103
104 pub fn as_slice(&self) -> &[u8] {
108 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
110 }
111
112 pub fn as_aligned_slice(&self) -> &[u8] {
117 let aligned_len = round_up(self.len, self.alignment);
118 let actual_len = aligned_len.min(self.capacity);
119 unsafe { std::slice::from_raw_parts(self.ptr, actual_len) }
121 }
122
123 pub fn clear(&mut self) {
125 self.len = 0;
126 }
127
128 pub fn len(&self) -> usize {
130 self.len
131 }
132
133 pub fn is_empty(&self) -> bool {
135 self.len == 0
136 }
137
138 pub fn capacity(&self) -> usize {
140 self.capacity
141 }
142
143 pub fn remaining(&self) -> usize {
145 self.capacity - self.len
146 }
147
148 pub fn alignment(&self) -> usize {
150 self.alignment
151 }
152
153 pub fn as_ptr(&self) -> *const u8 {
155 self.ptr
156 }
157
158 pub fn as_mut_ptr(&mut self) -> *mut u8 {
160 self.ptr
161 }
162}
163
164impl Drop for AlignedBuf {
165 fn drop(&mut self) {
166 unsafe {
168 std::alloc::dealloc(self.ptr, self.layout);
169 }
170 }
171}
172
173#[inline]
175pub const fn round_up(value: usize, align: usize) -> usize {
176 (value + align - 1) & !(align - 1)
177}
178
179#[inline]
181pub const fn is_aligned(value: usize, align: usize) -> bool {
182 value & (align - 1) == 0
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn round_up_works() {
191 assert_eq!(round_up(0, 4096), 0);
192 assert_eq!(round_up(1, 4096), 4096);
193 assert_eq!(round_up(4096, 4096), 4096);
194 assert_eq!(round_up(4097, 4096), 8192);
195 assert_eq!(round_up(8192, 4096), 8192);
196 }
197
198 #[test]
199 fn is_aligned_works() {
200 assert!(is_aligned(0, 4096));
201 assert!(is_aligned(4096, 4096));
202 assert!(is_aligned(8192, 4096));
203 assert!(!is_aligned(1, 4096));
204 assert!(!is_aligned(4097, 4096));
205 }
206
207 #[test]
208 fn aligned_buf_address_is_aligned() {
209 let buf = AlignedBuf::with_default_alignment(1).unwrap();
210 assert!(is_aligned(buf.as_ptr() as usize, DEFAULT_ALIGNMENT));
211 }
212
213 #[test]
214 fn aligned_buf_capacity_is_aligned() {
215 let buf = AlignedBuf::with_default_alignment(1).unwrap();
216 assert!(is_aligned(buf.capacity(), DEFAULT_ALIGNMENT));
217 assert!(buf.capacity() >= DEFAULT_ALIGNMENT);
218 }
219
220 #[test]
221 fn aligned_buf_write_and_read() {
222 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
223 let data = b"hello nodedb WAL";
224 let written = buf.write(data);
225 assert_eq!(written, data.len());
226 assert_eq!(&buf.as_slice()[..data.len()], data);
227 }
228
229 #[test]
230 fn aligned_slice_pads_to_boundary() {
231 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
232 buf.write(b"short");
233 assert_eq!(buf.len(), 5);
234 assert_eq!(buf.as_aligned_slice().len(), DEFAULT_ALIGNMENT);
235 }
236
237 #[test]
238 fn clear_resets_without_dealloc() {
239 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
240 let ptr_before = buf.as_ptr();
241 buf.write(b"some data");
242 buf.clear();
243 assert_eq!(buf.len(), 0);
244 assert!(buf.is_empty());
245 assert_eq!(buf.as_ptr(), ptr_before); }
247}