use crate::sync::{Condvar, Mutex};
use crate::tree_store::TransactionalMemory;
use crate::{Key, Result, Savepoint, TypeName, Value};
use alloc::collections::BTreeSet;
use alloc::collections::btree_map::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::mem;
use core::mem::size_of;
#[cfg(feature = "logging")]
use log::debug;
#[derive(Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub(crate) struct TransactionId(u64);
impl TransactionId {
pub(crate) fn new(value: u64) -> TransactionId {
Self(value)
}
pub(crate) fn raw_id(self) -> u64 {
self.0
}
pub(crate) fn next(self) -> TransactionId {
TransactionId(self.0 + 1)
}
pub(crate) fn increment(&mut self) -> TransactionId {
let next = self.next();
*self = next;
next
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub(crate) struct SavepointId(pub u64);
impl SavepointId {
pub(crate) fn next(self) -> SavepointId {
SavepointId(self.0 + 1)
}
}
impl Value for SavepointId {
type SelfType<'a> = SavepointId;
type AsBytes<'a> = [u8; size_of::<u64>()];
fn fixed_width() -> Option<usize> {
Some(size_of::<u64>())
}
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
where
Self: 'a,
{
SavepointId(u64::from_le_bytes(data.try_into().unwrap()))
}
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
where
Self: 'b,
{
value.0.to_le_bytes()
}
fn type_name() -> TypeName {
TypeName::internal("redb::SavepointId")
}
}
impl Key for SavepointId {
fn compare(data1: &[u8], data2: &[u8]) -> Ordering {
Self::from_bytes(data1).0.cmp(&Self::from_bytes(data2).0)
}
}
struct State {
next_savepoint_id: SavepointId,
live_read_transactions: BTreeMap<TransactionId, u64>,
next_transaction_id: TransactionId,
live_write_transaction: Option<TransactionId>,
valid_savepoints: BTreeMap<SavepointId, TransactionId>,
persistent_savepoints: BTreeSet<SavepointId>,
pending_non_durable_commits: BTreeMap<TransactionId, TransactionId>,
unprocessed_freed_non_durable_commits: BTreeSet<TransactionId>,
deferred_close: Option<Arc<TransactionalMemory>>,
}
pub(crate) struct TransactionTracker {
state: Mutex<State>,
live_write_transaction_available: Condvar,
}
impl TransactionTracker {
pub(crate) fn new(next_transaction_id: TransactionId) -> Self {
Self {
state: Mutex::new(State {
next_savepoint_id: SavepointId(0),
live_read_transactions: BTreeMap::default(),
next_transaction_id,
live_write_transaction: None,
valid_savepoints: BTreeMap::default(),
persistent_savepoints: BTreeSet::default(),
pending_non_durable_commits: BTreeMap::default(),
unprocessed_freed_non_durable_commits: BTreeSet::default(),
deferred_close: None,
}),
live_write_transaction_available: Condvar::new(),
}
}
pub(crate) fn start_write_transaction(&self) -> TransactionId {
let mut state = self.state.lock().unwrap();
while state.live_write_transaction.is_some() {
state = self.live_write_transaction_available.wait(state).unwrap();
}
assert!(state.live_write_transaction.is_none());
let transaction_id = state.next_transaction_id.increment();
#[cfg(feature = "logging")]
debug!("Beginning write transaction id={transaction_id:?}");
state.live_write_transaction = Some(transaction_id);
transaction_id
}
pub(crate) fn end_write_transaction(
&self,
id: TransactionId,
) -> Option<Arc<TransactionalMemory>> {
let mut state = self.state.lock().unwrap();
assert_eq!(state.live_write_transaction.unwrap(), id);
state.live_write_transaction = None;
self.live_write_transaction_available.notify_one();
state.deferred_close.take()
}
pub(crate) fn defer_close_if_write_transaction_live(
&self,
mem: &Arc<TransactionalMemory>,
) -> bool {
let mut state = self.state.lock().unwrap();
if state.live_write_transaction.is_some() {
state.deferred_close = Some(mem.clone());
true
} else {
false
}
}
pub(crate) fn clear_pending_non_durable_commits(&self) {
let mut state = self.state.lock().unwrap();
let ids = mem::take(&mut state.pending_non_durable_commits);
for (_, durable_ancestor) in ids {
let ref_count = state
.live_read_transactions
.get_mut(&durable_ancestor)
.unwrap();
*ref_count -= 1;
if *ref_count == 0 {
state.live_read_transactions.remove(&durable_ancestor);
}
}
}
pub(crate) fn is_unprocessed_non_durable_commit(&self, id: TransactionId) -> bool {
let state = self.state.lock().unwrap();
state.unprocessed_freed_non_durable_commits.contains(&id)
}
pub(crate) fn mark_non_durable_freed_pages_processed(
&self,
ids: impl IntoIterator<Item = TransactionId>,
) {
let mut state = self.state.lock().unwrap();
for id in ids {
state.unprocessed_freed_non_durable_commits.remove(&id);
}
}
pub(crate) fn oldest_unprocessed_non_durable_commit(&self) -> Option<TransactionId> {
let state = self.state.lock().unwrap();
state
.unprocessed_freed_non_durable_commits
.iter()
.next()
.copied()
}
pub(crate) fn register_non_durable_commit(
&self,
id: TransactionId,
durable_ancestor: TransactionId,
has_unprocessed_freed_pages: bool,
) {
let mut state = self.state.lock().unwrap();
state
.live_read_transactions
.entry(durable_ancestor)
.and_modify(|x| *x += 1)
.or_insert(1);
assert!(
state
.pending_non_durable_commits
.insert(id, durable_ancestor)
.is_none()
);
if has_unprocessed_freed_pages {
state.unprocessed_freed_non_durable_commits.insert(id);
}
}
pub(crate) fn reserve_transaction_id(
&self,
id: TransactionId,
live_write_transaction: TransactionId,
) {
let mut state = self.state.lock().unwrap();
assert_eq!(state.live_write_transaction, Some(live_write_transaction));
assert_eq!(id, state.next_transaction_id.next());
state.next_transaction_id = id;
}
pub(crate) fn restore_savepoint_counter_state(&self, next_savepoint: SavepointId) {
let mut state = self.state.lock().unwrap();
assert!(state.valid_savepoints.is_empty());
assert!(state.persistent_savepoints.is_empty());
state.next_savepoint_id = next_savepoint;
}
pub(crate) fn register_persistent_savepoint(&self, savepoint: &Savepoint) {
let mut state = self.state.lock().unwrap();
state
.live_read_transactions
.entry(savepoint.get_transaction_id())
.and_modify(|x| *x += 1)
.or_insert(1);
state
.valid_savepoints
.insert(savepoint.get_id(), savepoint.get_transaction_id());
state.persistent_savepoints.insert(savepoint.get_id());
}
pub(crate) fn mark_savepoint_persistent(&self, id: SavepointId) {
let mut state = self.state.lock().unwrap();
assert!(state.valid_savepoints.contains_key(&id));
state.persistent_savepoints.insert(id);
}
pub(crate) fn register_read_transaction(
&self,
mem: &TransactionalMemory,
) -> Result<TransactionId> {
let mut state = self.state.lock()?;
let id = mem.get_last_committed_transaction_id()?;
state
.live_read_transactions
.entry(id)
.and_modify(|x| *x += 1)
.or_insert(1);
Ok(id)
}
pub(crate) fn deallocate_read_transaction(&self, id: TransactionId) {
let mut state = self.state.lock().unwrap();
let ref_count = state.live_read_transactions.get_mut(&id).unwrap();
*ref_count -= 1;
if *ref_count == 0 {
state.live_read_transactions.remove(&id);
}
}
pub(crate) fn any_savepoint_exists(&self) -> bool {
!self.state.lock().unwrap().valid_savepoints.is_empty()
}
pub(crate) fn any_persistent_savepoint_exists(&self) -> bool {
!self.state.lock().unwrap().persistent_savepoints.is_empty()
}
pub(crate) fn any_ephemeral_savepoint_exists(&self) -> bool {
let state = self.state.lock().unwrap();
state
.valid_savepoints
.keys()
.any(|id| !state.persistent_savepoints.contains(id))
}
pub(crate) fn any_user_read_reference_exists(&self) -> bool {
let state = self.state.lock().unwrap();
for (id, count) in &state.live_read_transactions {
let pending_count = state
.pending_non_durable_commits
.values()
.filter(|ancestor| *ancestor == id)
.count() as u64;
if *count > pending_count {
return true;
}
}
false
}
pub(crate) fn allocate_savepoint(&self, transaction_id: TransactionId) -> SavepointId {
let mut state = self.state.lock().unwrap();
let id = state.next_savepoint_id.next();
state.next_savepoint_id = id;
state.valid_savepoints.insert(id, transaction_id);
id
}
pub(crate) fn deallocate_savepoint(&self, savepoint: SavepointId, transaction: TransactionId) {
{
let mut state = self.state.lock().unwrap();
state.valid_savepoints.remove(&savepoint);
state.persistent_savepoints.remove(&savepoint);
}
self.deallocate_read_transaction(transaction);
}
pub(crate) fn is_valid_savepoint(&self, id: SavepointId) -> bool {
self.state
.lock()
.unwrap()
.valid_savepoints
.contains_key(&id)
}
pub(crate) fn list_savepoints_after(&self, id: SavepointId) -> Vec<SavepointId> {
self.state
.lock()
.unwrap()
.valid_savepoints
.range((
core::ops::Bound::Excluded(id),
core::ops::Bound::Unbounded::<SavepointId>,
))
.map(|(x, _)| *x)
.collect()
}
pub(crate) fn invalidate_savepoints(&self, savepoints: impl IntoIterator<Item = SavepointId>) {
let mut state = self.state.lock().unwrap();
for id in savepoints {
state.valid_savepoints.remove(&id);
state.persistent_savepoints.remove(&id);
}
}
pub(crate) fn oldest_savepoint_excluding(
&self,
exclude: &BTreeSet<SavepointId>,
) -> Option<(SavepointId, TransactionId)> {
self.state
.lock()
.unwrap()
.valid_savepoints
.iter()
.find(|(id, _)| !exclude.contains(id))
.map(|(id, txn_id)| (*id, *txn_id))
}
pub(crate) fn oldest_live_read_transaction(&self) -> Option<TransactionId> {
self.state
.lock()
.unwrap()
.live_read_transactions
.keys()
.next()
.copied()
}
pub(crate) fn oldest_live_read_nondurable_transaction(&self) -> Option<TransactionId> {
let state = self.state.lock().unwrap();
for id in state.live_read_transactions.keys() {
if state.pending_non_durable_commits.contains_key(id) {
return Some(*id);
}
}
None
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn non_durable_commit_without_freed_pages_is_not_unprocessed() {
let tracker = TransactionTracker::new(TransactionId::new(0));
tracker.register_non_durable_commit(TransactionId::new(1), TransactionId::new(0), false);
assert_eq!(None, tracker.oldest_unprocessed_non_durable_commit());
tracker.register_non_durable_commit(TransactionId::new(2), TransactionId::new(0), true);
assert_eq!(
Some(TransactionId::new(2)),
tracker.oldest_unprocessed_non_durable_commit()
);
}
}