use crate::types::{Result, TransactionId, VelociError};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum TransactionState {
Active = 0,
Committed = 1,
Aborted = 2,
}
impl From<u8> for TransactionState {
fn from(value: u8) -> Self {
match value {
0 => TransactionState::Active,
1 => TransactionState::Committed,
2 => TransactionState::Aborted,
_ => TransactionState::Aborted,
}
}
}
pub struct Transaction {
id: TransactionId,
state: AtomicU8,
}
impl Transaction {
pub fn new(id: TransactionId) -> Self {
Self {
id,
state: AtomicU8::new(TransactionState::Active as u8),
}
}
pub fn id(&self) -> TransactionId {
self.id
}
pub fn state(&self) -> TransactionState {
self.state.load(Ordering::Acquire).into()
}
pub fn commit(&self) -> Result<()> {
match self.state.compare_exchange(
TransactionState::Active as u8,
TransactionState::Committed as u8,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => Ok(()),
Err(state) => {
let current_state: TransactionState = state.into();
Err(VelociError::TransactionError(
format!("Transaction is not active, current state: {:?}", current_state),
))
}
}
}
pub fn abort(&self) -> Result<()> {
match self.state.compare_exchange(
TransactionState::Active as u8,
TransactionState::Aborted as u8,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => Ok(()),
Err(state) => {
let current_state: TransactionState = state.into();
Err(VelociError::TransactionError(
format!("Transaction is not active, current state: {:?}", current_state),
))
}
}
}
}
pub struct TransactionManager {
next_txn_id: AtomicU64,
active_transactions: RwLock<HashMap<TransactionId, Arc<Transaction>>>,
}
impl TransactionManager {
pub fn new() -> Self {
Self {
next_txn_id: AtomicU64::new(1),
active_transactions: RwLock::new(HashMap::new()),
}
}
pub fn begin(&self) -> Arc<Transaction> {
let txn_id = self.next_txn_id.fetch_add(1, Ordering::SeqCst);
let txn = Arc::new(Transaction::new(txn_id));
self.active_transactions
.write()
.insert(txn_id, Arc::clone(&txn));
txn
}
pub fn commit(&self, txn: &Transaction) -> Result<()> {
txn.commit()?;
self.active_transactions.write().remove(&txn.id());
Ok(())
}
pub fn abort(&self, txn: &Transaction) -> Result<()> {
txn.abort()?;
self.active_transactions.write().remove(&txn.id());
Ok(())
}
pub fn get_transaction(&self, txn_id: TransactionId) -> Option<Arc<Transaction>> {
self.active_transactions.read().get(&txn_id).cloned()
}
pub fn active_count(&self) -> usize {
self.active_transactions.read().len()
}
pub fn try_get_transaction(&self, txn_id: TransactionId, timeout: Duration) -> Option<Arc<Transaction>> {
self.active_transactions
.try_read_for(timeout)
.and_then(|guard| guard.get(&txn_id).cloned())
}
}
use dashmap::DashMap;
pub struct LockManager {
locks: DashMap<String, Vec<LockEntry>>,
}
#[derive(Debug, Clone)]
struct LockEntry {
txn_id: TransactionId,
lock_type: LockType,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LockType {
Shared,
Exclusive,
}
impl LockManager {
pub fn new() -> Self {
Self {
locks: DashMap::new(),
}
}
pub fn acquire_lock(&self, resource: &str, txn_id: TransactionId, lock_type: LockType) -> Result<()> {
self.try_acquire_lock_with_timeout(resource, txn_id, lock_type, Duration::from_secs(30))
}
pub fn try_acquire_lock_with_timeout(
&self,
resource: &str,
txn_id: TransactionId,
lock_type: LockType,
timeout: Duration,
) -> Result<()> {
let start = std::time::Instant::now();
loop {
let result = self.try_acquire_lock_once(resource, txn_id, lock_type);
match result {
Ok(()) => return Ok(()),
Err(VelociError::Busy) => {
if start.elapsed() > timeout {
return Err(VelociError::TransactionError(
format!("Lock acquisition timeout after {:?} for resource '{}'", timeout, resource)
));
}
let backoff = std::cmp::min(start.elapsed().as_millis() / 10, 100) as u64;
std::thread::sleep(Duration::from_micros(backoff));
}
Err(e) => return Err(e),
}
}
}
fn try_acquire_lock_once(&self, resource: &str, txn_id: TransactionId, lock_type: LockType) -> Result<()> {
let mut entry = self.locks.entry(resource.to_string()).or_insert_with(Vec::new);
let entries = entry.value_mut();
for existing in entries.iter() {
if existing.txn_id == txn_id {
match (existing.lock_type, lock_type) {
(LockType::Shared, LockType::Exclusive) => {
entries.retain(|e| e.txn_id != txn_id);
break;
}
(LockType::Exclusive, LockType::Shared) => {
return Err(VelociError::ConstraintViolation(
"Cannot downgrade exclusive lock to shared".to_string()
));
}
(LockType::Shared, LockType::Shared) | (LockType::Exclusive, LockType::Exclusive) => {
return Ok(());
}
}
} else {
match (lock_type, existing.lock_type) {
(LockType::Shared, LockType::Shared) => {
}
(LockType::Exclusive, _) | (_, LockType::Exclusive) => {
return Err(VelociError::Busy);
}
}
}
}
entries.push(LockEntry { txn_id, lock_type });
Ok(())
}
pub fn release_lock(&self, resource: &str, txn_id: TransactionId) -> Result<()> {
if let Some(mut entry) = self.locks.get_mut(resource) {
entry.value_mut().retain(|e| e.txn_id != txn_id);
if entry.value().is_empty() {
drop(entry);
self.locks.remove(resource);
}
}
Ok(())
}
pub fn release_all_locks(&self, txn_id: TransactionId) {
let keys: Vec<String> = self.locks.iter().map(|r| r.key().clone()).collect();
for key in keys {
let _ = self.release_lock(&key, txn_id);
}
}
pub fn has_locks(&self, txn_id: TransactionId) -> bool {
self.locks.iter().any(|entry| {
entry.value().iter().any(|e| e.txn_id == txn_id)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_lifecycle() {
let txn_mgr = TransactionManager::new();
let txn = txn_mgr.begin();
assert_eq!(txn.state(), TransactionState::Active);
txn_mgr.commit(&txn).unwrap();
assert_eq!(txn.state(), TransactionState::Committed);
}
#[test]
fn test_lock_manager() {
let lock_mgr = LockManager::new();
lock_mgr.acquire_lock("table1", 1, LockType::Shared).unwrap();
lock_mgr.acquire_lock("table1", 2, LockType::Shared).unwrap();
let result = lock_mgr.acquire_lock("table1", 3, LockType::Exclusive);
assert!(result.is_err());
}
}