use core::sync::atomic::{AtomicU64, Ordering};
use crate::bytes;
use crate::error::{no_mem, DbResult};
use crate::page::PageSize;
static BUFFERS_ALLOCATED: AtomicU64 = AtomicU64::new(0);
static BYTES_ALLOCATED: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AllocationStats {
pub buffers: u64,
pub bytes: u64,
}
impl AllocationStats {
pub fn since(self, earlier: AllocationStats) -> AllocationStats {
AllocationStats {
buffers: self.buffers.saturating_sub(earlier.buffers),
bytes: self.bytes.saturating_sub(earlier.bytes),
}
}
}
pub fn allocation_stats() -> AllocationStats {
AllocationStats {
buffers: BUFFERS_ALLOCATED.load(Ordering::Relaxed),
bytes: BYTES_ALLOCATED.load(Ordering::Relaxed),
}
}
thread_local! {
static FAIL_ALLOCATION_AT: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
static ALLOCATIONS_SINCE_ARMED: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}
pub fn fail_allocation_after(n: u64) {
ALLOCATIONS_SINCE_ARMED.with(|count| count.set(0));
FAIL_ALLOCATION_AT.with(|slot| slot.set(n));
}
pub fn clear_allocation_failpoint() {
FAIL_ALLOCATION_AT.with(|slot| slot.set(0));
}
pub fn allocations_since_armed() -> u64 {
ALLOCATIONS_SINCE_ARMED.with(core::cell::Cell::get)
}
fn allocation_is_refused() -> bool {
let armed = FAIL_ALLOCATION_AT.with(core::cell::Cell::get);
if armed == 0 {
return false;
}
let index = ALLOCATIONS_SINCE_ARMED.with(|count| {
let next = count.get().saturating_add(1);
count.set(next);
next
});
index == armed
}
pub fn try_zeroed(len: usize) -> DbResult<Box<[u8]>> {
if allocation_is_refused() {
return Err(no_mem("an injected allocation failure"));
}
let mut buffer: Vec<u8> = Vec::new();
buffer
.try_reserve_exact(len)
.map_err(|_| no_mem("cannot allocate a byte buffer"))?;
buffer.resize(len, 0);
BUFFERS_ALLOCATED.fetch_add(1, Ordering::Relaxed);
BYTES_ALLOCATED.fetch_add(len as u64, Ordering::Relaxed);
Ok(buffer.into_boxed_slice())
}
pub fn try_copy_of(source: &[u8]) -> DbResult<Box<[u8]>> {
let mut buffer = try_zeroed(source.len())?;
for (slot, byte) in buffer.iter_mut().zip(source.iter()) {
*slot = *byte;
}
Ok(buffer)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PageBuffer {
size: PageSize,
bytes: Box<[u8]>,
}
impl PageBuffer {
pub fn zeroed(size: PageSize) -> DbResult<PageBuffer> {
Ok(PageBuffer {
size,
bytes: try_zeroed(size.as_usize())?,
})
}
pub fn from_bytes(size: PageSize, bytes: Box<[u8]>) -> DbResult<PageBuffer> {
if bytes.len() != size.as_usize() {
return Err(crate::error::corrupt(
"page buffer is not exactly one page long",
));
}
Ok(PageBuffer { size, bytes })
}
pub fn size(&self) -> PageSize {
self.size
}
pub fn as_slice(&self) -> &[u8] {
&self.bytes
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.bytes
}
pub fn read_u16(&self, offset: usize) -> DbResult<u16> {
bytes::read_u16(&self.bytes, offset)
}
pub fn read_u32(&self, offset: usize) -> DbResult<u32> {
bytes::read_u32(&self.bytes, offset)
}
pub fn write_u16(&mut self, offset: usize, value: u16) -> DbResult<()> {
bytes::write_u16(&mut self.bytes, offset, value)
}
pub fn write_u32(&mut self, offset: usize, value: u32) -> DbResult<()> {
bytes::write_u32(&mut self.bytes, offset, value)
}
pub fn clear(&mut self) {
for byte in self.bytes.iter_mut() {
*byte = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::PrimaryCode;
#[test]
fn a_page_buffer_is_exactly_one_page_of_zeroes() {
let size = PageSize::new(4096).unwrap();
let page = PageBuffer::zeroed(size).unwrap();
assert_eq!(page.as_slice().len(), 4096);
assert!(page.as_slice().iter().all(|byte| *byte == 0));
}
#[test]
fn wrapping_the_wrong_length_is_refused() {
let size = PageSize::new(1024).unwrap();
let short = try_zeroed(1023).unwrap();
assert_eq!(
PageBuffer::from_bytes(size, short).unwrap_err().code(),
PrimaryCode::Corrupt
);
}
#[cfg_attr(miri, ignore)]
#[test]
fn an_impossible_allocation_returns_nomem() {
let error = try_zeroed(usize::MAX / 2).expect_err("this cannot be allocated");
assert_eq!(error.code(), PrimaryCode::NoMem);
}
#[test]
fn allocation_counters_track_what_was_allocated() {
let before = allocation_stats();
let _first = try_zeroed(4096).unwrap();
let _second = try_zeroed(1024).unwrap();
let delta = allocation_stats().since(before);
assert!(delta.buffers >= 2, "{delta:?}");
assert!(delta.bytes >= 5120, "{delta:?}");
}
#[test]
fn field_accessors_are_bounds_checked() {
let size = PageSize::new(512).unwrap();
let mut page = PageBuffer::zeroed(size).unwrap();
page.write_u32(508, 0xdead_beef).unwrap();
assert_eq!(page.read_u32(508).unwrap(), 0xdead_beef);
assert_eq!(page.read_u32(509).unwrap_err().code(), PrimaryCode::Corrupt);
page.clear();
assert_eq!(page.read_u32(508).unwrap(), 0);
}
}