use crate::error::{Result, WalError};
pub const DEFAULT_ALIGNMENT: usize = 4096;
pub struct AlignedBuf {
ptr: *mut u8,
capacity: usize,
len: usize,
alignment: usize,
layout: std::alloc::Layout,
}
unsafe impl Send for AlignedBuf {}
impl AlignedBuf {
pub fn new(min_capacity: usize, alignment: usize) -> Result<Self> {
assert!(
alignment.is_power_of_two(),
"alignment must be power of two"
);
assert!(alignment > 0, "alignment must be > 0");
let capacity = round_up(min_capacity.max(alignment), alignment);
let layout = std::alloc::Layout::from_size_align(capacity, alignment).map_err(|_| {
WalError::AlignmentViolation {
context: "buffer allocation",
required: alignment,
actual: min_capacity,
}
})?;
let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
Ok(Self {
ptr,
capacity,
len: 0,
alignment,
layout,
})
}
pub fn with_default_alignment(min_capacity: usize) -> Result<Self> {
Self::new(min_capacity, DEFAULT_ALIGNMENT)
}
pub fn write(&mut self, data: &[u8]) -> usize {
let available = self.capacity - self.len;
let to_write = data.len().min(available);
if to_write > 0 {
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(self.len), to_write);
}
self.len += to_write;
}
to_write
}
pub fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
pub fn as_aligned_slice(&self) -> &[u8] {
let aligned_len = round_up(self.len, self.alignment);
let actual_len = aligned_len.min(self.capacity);
unsafe { std::slice::from_raw_parts(self.ptr, actual_len) }
}
pub fn clear(&mut self) {
self.len = 0;
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn remaining(&self) -> usize {
self.capacity - self.len
}
pub fn alignment(&self) -> usize {
self.alignment
}
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
}
impl Drop for AlignedBuf {
fn drop(&mut self) {
unsafe {
std::alloc::dealloc(self.ptr, self.layout);
}
}
}
#[inline]
pub const fn round_up(value: usize, align: usize) -> usize {
(value + align - 1) & !(align - 1)
}
#[inline]
pub const fn is_aligned(value: usize, align: usize) -> bool {
value & (align - 1) == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_up_works() {
assert_eq!(round_up(0, 4096), 0);
assert_eq!(round_up(1, 4096), 4096);
assert_eq!(round_up(4096, 4096), 4096);
assert_eq!(round_up(4097, 4096), 8192);
assert_eq!(round_up(8192, 4096), 8192);
}
#[test]
fn is_aligned_works() {
assert!(is_aligned(0, 4096));
assert!(is_aligned(4096, 4096));
assert!(is_aligned(8192, 4096));
assert!(!is_aligned(1, 4096));
assert!(!is_aligned(4097, 4096));
}
#[test]
fn aligned_buf_address_is_aligned() {
let buf = AlignedBuf::with_default_alignment(1).unwrap();
assert!(is_aligned(buf.as_ptr() as usize, DEFAULT_ALIGNMENT));
}
#[test]
fn aligned_buf_capacity_is_aligned() {
let buf = AlignedBuf::with_default_alignment(1).unwrap();
assert!(is_aligned(buf.capacity(), DEFAULT_ALIGNMENT));
assert!(buf.capacity() >= DEFAULT_ALIGNMENT);
}
#[test]
fn aligned_buf_write_and_read() {
let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
let data = b"hello nodedb WAL";
let written = buf.write(data);
assert_eq!(written, data.len());
assert_eq!(&buf.as_slice()[..data.len()], data);
}
#[test]
fn aligned_slice_pads_to_boundary() {
let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
buf.write(b"short");
assert_eq!(buf.len(), 5);
assert_eq!(buf.as_aligned_slice().len(), DEFAULT_ALIGNMENT);
}
#[test]
fn clear_resets_without_dealloc() {
let mut buf = AlignedBuf::with_default_alignment(8192).unwrap();
let ptr_before = buf.as_ptr();
buf.write(b"some data");
buf.clear();
assert_eq!(buf.len(), 0);
assert!(buf.is_empty());
assert_eq!(buf.as_ptr(), ptr_before); }
}