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] {
119 let aligned_len = round_up(self.len, self.alignment);
120 let actual_len = aligned_len.min(self.capacity);
121 unsafe { std::slice::from_raw_parts(self.ptr, actual_len) }
123 }
124
125 pub fn clear(&mut self) {
127 self.len = 0;
128 }
129
130 pub fn len(&self) -> usize {
132 self.len
133 }
134
135 pub fn is_empty(&self) -> bool {
137 self.len == 0
138 }
139
140 pub fn capacity(&self) -> usize {
142 self.capacity
143 }
144
145 pub fn remaining(&self) -> usize {
147 self.capacity - self.len
148 }
149
150 pub fn alignment(&self) -> usize {
152 self.alignment
153 }
154
155 pub fn as_ptr(&self) -> *const u8 {
157 self.ptr
158 }
159
160 pub fn as_mut_ptr(&mut self) -> *mut u8 {
162 self.ptr
163 }
164}
165
166impl Drop for AlignedBuf {
167 fn drop(&mut self) {
168 unsafe {
170 std::alloc::dealloc(self.ptr, self.layout);
171 }
172 }
173}
174
175#[inline]
177pub const fn round_up(value: usize, align: usize) -> usize {
178 (value + align - 1) & !(align - 1)
179}
180
181#[inline]
183pub const fn is_aligned(value: usize, align: usize) -> bool {
184 value & (align - 1) == 0
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn round_up_works() {
193 assert_eq!(round_up(0, 4096), 0);
194 assert_eq!(round_up(1, 4096), 4096);
195 assert_eq!(round_up(4096, 4096), 4096);
196 assert_eq!(round_up(4097, 4096), 8192);
197 assert_eq!(round_up(8192, 4096), 8192);
198 }
199
200 #[test]
201 fn is_aligned_works() {
202 assert!(is_aligned(0, 4096));
203 assert!(is_aligned(4096, 4096));
204 assert!(is_aligned(8192, 4096));
205 assert!(!is_aligned(1, 4096));
206 assert!(!is_aligned(4097, 4096));
207 }
208
209 #[test]
210 fn aligned_buf_address_is_aligned() {
211 let buf = AlignedBuf::with_default_alignment(1).unwrap();
212 assert!(is_aligned(buf.as_ptr() as usize, DEFAULT_ALIGNMENT));
213 }
214
215 #[test]
216 fn aligned_buf_capacity_is_aligned() {
217 let buf = AlignedBuf::with_default_alignment(1).unwrap();
218 assert!(is_aligned(buf.capacity(), DEFAULT_ALIGNMENT));
219 assert!(buf.capacity() >= DEFAULT_ALIGNMENT);
220 }
221
222 #[test]
223 fn aligned_buf_write_and_read() {
224 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
225 let data = b"hello nodedb WAL";
226 let written = buf.write(data);
227 assert_eq!(written, data.len());
228 assert_eq!(&buf.as_slice()[..data.len()], data);
229 }
230
231 #[test]
232 fn aligned_slice_pads_to_boundary() {
233 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
234 buf.write(b"short");
235 assert_eq!(buf.len(), 5);
236 assert_eq!(buf.as_aligned_slice().len(), DEFAULT_ALIGNMENT);
237 }
238
239 #[test]
240 fn clear_resets_without_dealloc() {
241 let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
242 let ptr_before = buf.as_ptr();
243 buf.write(b"some data");
244 buf.clear();
245 assert_eq!(buf.len(), 0);
246 assert!(buf.is_empty());
247 assert_eq!(buf.as_ptr(), ptr_before); }
249}