use core::mem::{self, ManuallyDrop};
use core::ptr::NonNull;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::arena::allocator::CACHE_LINE_SIZE;
use crate::arena::{Arena, Checkpoint};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use = "transaction status indicates whether the operation succeeded"]
pub enum TxnStatus {
Committed,
RolledBack,
}
impl std::fmt::Display for TxnStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TxnStatus::Committed => f.write_str("committed"),
TxnStatus::RolledBack => f.write_str("rolled back"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[must_use = "transaction diff provides allocation metrics"]
pub struct TxnDiff {
pub bytes_allocated: usize,
pub blocks_touched: usize,
}
impl std::fmt::Display for TxnDiff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} bytes in {} block(s)",
self.bytes_allocated, self.blocks_touched
)
}
}
pub struct Transaction<'arena> {
pub(crate) arena: &'arena mut Arena,
checkpoint: Checkpoint,
committed: bool,
bytes_at_open: usize,
block_at_open: usize,
depth: usize,
limit: Option<usize>,
}
impl<'arena> Transaction<'arena> {
pub(crate) fn new(arena: &'arena mut Arena) -> Self {
let checkpoint = arena.checkpoint();
let bytes_at_open = checkpoint.bytes_allocated;
let block_at_open = checkpoint.block_idx;
arena.txn_depth += 1;
let depth = arena.txn_depth;
Transaction {
arena,
checkpoint,
committed: false,
bytes_at_open,
block_at_open,
depth,
limit: None,
}
}
}
impl<'arena> Transaction<'arena> {
pub fn commit(self) -> TxnStatus {
let mut this = ManuallyDrop::new(self);
this.arena.txn_depth = this.arena.txn_depth.wrapping_sub(1);
TxnStatus::Committed
}
pub fn rollback(self) -> TxnStatus {
drop(self);
TxnStatus::RolledBack
}
pub fn savepoint(&mut self) -> Transaction<'_> {
Transaction::new(self.arena)
}
}
impl<'arena> Transaction<'arena> {
pub fn set_limit(&mut self, bytes: usize) {
self.limit = Some(bytes);
}
#[must_use]
pub fn budget_remaining(&self) -> Option<usize> {
self.limit.map(|lim| lim.saturating_sub(self.bytes_used()))
}
#[inline]
fn budget_ok(&self, additional: usize) -> bool {
match self.limit {
None => true,
Some(lim) => self.bytes_used().saturating_add(additional) <= lim,
}
}
#[cold]
fn budget_panic(&self, n: usize) -> ! {
panic!(
"arena transaction budget exceeded: limit={}, used={}, requested={}",
self.limit.unwrap_or(0),
self.bytes_used(),
n
)
}
}
impl<'arena> Transaction<'arena> {
#[inline(always)]
#[must_use]
pub fn bytes_used(&self) -> usize {
let current_used = self.arena.cur_ptr as usize - self.arena.cur_base as usize;
(self.arena.bytes_allocated + current_used).saturating_sub(self.bytes_at_open)
}
#[inline(always)]
#[must_use]
pub fn is_committed(&self) -> bool {
self.committed
}
#[inline(always)]
#[must_use]
pub fn depth(&self) -> usize {
self.depth
}
#[inline(always)]
#[must_use]
pub fn arena_depth(&self) -> usize {
self.arena.txn_depth
}
#[inline(always)]
pub fn checkpoint(&self) -> Checkpoint {
self.checkpoint
}
#[inline]
pub fn arena_mut(&mut self) -> &mut Arena {
self.arena
}
pub fn diff(&self) -> TxnDiff {
TxnDiff {
bytes_allocated: self.bytes_used(),
blocks_touched: self.arena.current.saturating_sub(self.block_at_open) + 1,
}
}
}
impl<'arena> Transaction<'arena> {
#[inline]
pub fn alloc<T>(&mut self, val: T) -> &mut T {
if !self.budget_ok(mem::size_of::<T>()) {
self.budget_panic(mem::size_of::<T>());
}
self.arena.alloc(val)
}
#[inline]
pub fn alloc_slice<T, I>(&mut self, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let iter = iter.into_iter();
let approx = mem::size_of::<T>().saturating_mul(iter.len());
if !self.budget_ok(approx) {
self.budget_panic(approx);
}
self.arena.alloc_slice(iter)
}
#[inline]
pub fn alloc_str(&mut self, s: &str) -> &str {
if !self.budget_ok(s.len()) {
self.budget_panic(s.len());
}
self.arena.alloc_str(s)
}
#[inline]
pub fn alloc_uninit<T>(&mut self) -> &mut std::mem::MaybeUninit<T> {
if !self.budget_ok(mem::size_of::<T>()) {
self.budget_panic(mem::size_of::<T>());
}
self.arena.alloc_uninit::<T>()
}
#[inline]
pub fn alloc_zeroed(&mut self, size: usize, align: usize) -> NonNull<u8> {
if !self.budget_ok(size) {
self.budget_panic(size);
}
self.arena.alloc_zeroed(size, align)
}
#[inline]
pub fn alloc_cache_aligned(&mut self, size: usize) -> NonNull<u8> {
if !self.budget_ok(size) {
self.budget_panic(size);
}
self.arena.alloc_raw(size, CACHE_LINE_SIZE)
}
#[inline]
pub fn alloc_raw(&mut self, size: usize, align: usize) -> NonNull<u8> {
if !self.budget_ok(size) {
self.budget_panic(size);
}
self.arena.alloc_raw(size, align)
}
#[inline]
pub fn alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> &mut [T] {
let total = mem::size_of::<T>().saturating_mul(src.len());
if !self.budget_ok(total) {
self.budget_panic(total);
}
self.arena.alloc_slice_copy(src)
}
#[inline]
pub fn try_alloc<T>(&mut self, val: T) -> Option<&mut T> {
if !self.budget_ok(mem::size_of::<T>()) {
return None;
}
self.arena.try_alloc(val)
}
#[inline]
pub fn try_alloc_slice<T, I>(&mut self, iter: I) -> Option<&mut [T]>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let iter = iter.into_iter();
let approx = mem::size_of::<T>().saturating_mul(iter.len());
if !self.budget_ok(approx) {
return None;
}
self.arena.try_alloc_slice(iter)
}
#[inline]
pub fn try_alloc_str(&mut self, s: &str) -> Option<&str> {
if !self.budget_ok(s.len()) {
return None;
}
self.arena.try_alloc_str(s)
}
#[inline]
pub fn try_alloc_raw(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
if !self.budget_ok(size) {
return None;
}
self.arena.try_alloc_raw(size, align)
}
#[inline]
pub fn try_alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> Option<&mut [T]> {
let total = mem::size_of::<T>().saturating_mul(src.len());
if !self.budget_ok(total) {
return None;
}
self.arena.try_alloc_slice_copy(src)
}
#[inline]
pub fn try_alloc_cache_aligned(&mut self, size: usize) -> Option<NonNull<u8>> {
if !self.budget_ok(size) {
return None;
}
self.arena.try_alloc_raw(size, CACHE_LINE_SIZE)
}
}
impl Drop for Transaction<'_> {
fn drop(&mut self) {
self.arena.txn_depth = self.arena.txn_depth.saturating_sub(1);
if !self.committed {
#[cold]
fn do_rewind(arena: &mut Arena, checkpoint: Checkpoint) {
arena.rewind(checkpoint);
}
do_rewind(self.arena, self.checkpoint);
}
}
}
pub(crate) fn run_with_transaction<'arena, F, T, E>(arena: &'arena mut Arena, f: F) -> Result<T, E>
where
F: FnOnce(&mut Transaction<'arena>) -> Result<T, E>,
{
let mut txn = Transaction::new(arena);
match f(&mut txn) {
Ok(val) => {
let _ = txn.commit();
Ok(val)
}
Err(e) => Err(e),
}
}
pub(crate) fn run_with_transaction_infallible<'arena, F, T>(arena: &'arena mut Arena, f: F) -> T
where
F: FnOnce(&mut Transaction<'arena>) -> T,
{
let mut txn = Transaction::new(arena);
let result = catch_unwind(AssertUnwindSafe(|| f(&mut txn)));
let _ = txn.commit();
match result {
Ok(val) => val,
Err(payload) => std::panic::panic_any(payload),
}
}