1use crate::error::{Result, WalError};
13
14pub const DEFAULT_ALIGNMENT: usize = 4096;
20
21pub struct AlignedBuf {
26 ptr: *mut u8,
28
29 capacity: usize,
31
32 len: usize,
34
35 alignment: usize,
37
38 layout: std::alloc::Layout,
40}
41
42unsafe impl Send for AlignedBuf {}
45
46impl AlignedBuf {
47 pub fn new(min_capacity: usize, alignment: usize) -> Result<Self> {
52 assert!(
53 alignment.is_power_of_two(),
54 "alignment must be power of two"
55 );
56 assert!(alignment > 0, "alignment must be > 0");
57
58 let capacity = round_up(min_capacity.max(alignment), alignment);
60
61 let layout = std::alloc::Layout::from_size_align(capacity, alignment).map_err(|_| {
62 WalError::AlignmentViolation {
63 context: "buffer allocation",
64 required: alignment,
65 actual: min_capacity,
66 }
67 })?;
68
69 let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
71 if ptr.is_null() {
72 std::alloc::handle_alloc_error(layout);
73 }
74
75 Ok(Self {
76 ptr,
77 capacity,
78 len: 0,
79 alignment,
80 layout,
81 })
82 }
83
84 pub fn with_default_alignment(min_capacity: usize) -> Result<Self> {
86 Self::new(min_capacity, DEFAULT_ALIGNMENT)
87 }
88
89 pub fn write(&mut self, data: &[u8]) -> usize {
94 let available = self.capacity - self.len;
95 let to_write = data.len().min(available);
96 if to_write > 0 {
97 unsafe {
99 std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(self.len), to_write);
100 }
101 self.len += to_write;
102 }
103 to_write
104 }
105
106 pub fn as_slice(&self) -> &[u8] {
110 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
112 }
113
114 pub fn as_aligned_slice(&self) -> &[u8] {
123 let aligned_len = round_up(self.len, self.alignment);
124 let actual_len = aligned_len.min(self.capacity);
125 unsafe { std::slice::from_raw_parts(self.ptr, actual_len) }
127 }
128
129 pub fn clear(&mut self) {
131 self.len = 0;
132 }
133
134 pub fn len(&self) -> usize {
136 self.len
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.len == 0
142 }
143
144 pub fn capacity(&self) -> usize {
146 self.capacity
147 }
148
149 pub fn remaining(&self) -> usize {
151 self.capacity - self.len
152 }
153
154 pub fn alignment(&self) -> usize {
156 self.alignment
157 }
158
159 pub fn as_ptr(&self) -> *const u8 {
161 self.ptr
162 }
163
164 pub fn as_mut_ptr(&mut self) -> *mut u8 {
166 self.ptr
167 }
168}
169
170impl Drop for AlignedBuf {
171 fn drop(&mut self) {
172 unsafe {
174 std::alloc::dealloc(self.ptr, self.layout);
175 }
176 }
177}
178
179#[inline]
181pub const fn round_up(value: usize, align: usize) -> usize {
182 (value + align - 1) & !(align - 1)
183}
184
185#[inline]
187pub const fn is_aligned(value: usize, align: usize) -> bool {
188 value & (align - 1) == 0
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn round_up_works() {
197 assert_eq!(round_up(0, 4096), 0);
198 assert_eq!(round_up(1, 4096), 4096);
199 assert_eq!(round_up(4096, 4096), 4096);
200 assert_eq!(round_up(4097, 4096), 8192);
201 assert_eq!(round_up(8192, 4096), 8192);
202 }
203
204 #[test]
205 fn is_aligned_works() {
206 assert!(is_aligned(0, 4096));
207 assert!(is_aligned(4096, 4096));
208 assert!(is_aligned(8192, 4096));
209 assert!(!is_aligned(1, 4096));
210 assert!(!is_aligned(4097, 4096));
211 }
212
213 #[test]
214 fn aligned_buf_address_is_aligned() {
215 let buf = AlignedBuf::with_default_alignment(1).unwrap();
216 assert!(is_aligned(buf.as_ptr() as usize, DEFAULT_ALIGNMENT));
217 }
218
219 #[test]
220 fn aligned_buf_capacity_is_aligned() {
221 let buf = AlignedBuf::with_default_alignment(1).unwrap();
222 assert!(is_aligned(buf.capacity(), DEFAULT_ALIGNMENT));
223 assert!(buf.capacity() >= DEFAULT_ALIGNMENT);
224 }
225
226 #[test]
227 fn aligned_buf_write_and_read() {
228 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
229 let data = b"hello nodedb WAL";
230 let written = buf.write(data);
231 assert_eq!(written, data.len());
232 assert_eq!(&buf.as_slice()[..data.len()], data);
233 }
234
235 #[test]
236 fn aligned_slice_pads_to_boundary() {
237 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
238 buf.write(b"short");
239 assert_eq!(buf.len(), 5);
240 assert_eq!(buf.as_aligned_slice().len(), DEFAULT_ALIGNMENT);
241 }
242
243 #[test]
244 fn clear_resets_without_dealloc() {
245 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
246 let ptr_before = buf.as_ptr();
247 buf.write(b"some data");
248 buf.clear();
249 assert_eq!(buf.len(), 0);
250 assert!(buf.is_empty());
251 assert_eq!(buf.as_ptr(), ptr_before); }
253}