Files
Oxicloud/src/application/transactions/storage_transaction.rs
T

130 lines
5.0 KiB
Rust
Raw Normal View History

2025-03-19 00:44:27 +01:00
use std::future::Future;
use std::pin::Pin;
use crate::common::errors::{DomainError, ErrorKind};
/// Type for async operations and rollbacks
2025-03-19 00:44:27 +01:00
type TransactionOp = Pin<Box<dyn Future<Output = Result<(), DomainError>> + Send>>;
/// Transaction for storage operations
/// Allows defining a set of operations and their corresponding rollbacks
2025-03-19 00:44:27 +01:00
pub struct StorageTransaction {
/// Operations to execute
2025-03-19 00:44:27 +01:00
operations: Vec<Box<dyn FnOnce() -> TransactionOp + Send>>,
/// Rollback operations to revert changes in case of error
2025-03-19 00:44:27 +01:00
rollbacks: Vec<Box<dyn FnOnce() -> TransactionOp + Send>>,
/// Transaction name for logging
2025-03-19 00:44:27 +01:00
name: String,
}
impl StorageTransaction {
/// Creates a new transaction
2025-03-19 00:44:27 +01:00
pub fn new(name: &str) -> Self {
Self {
operations: Vec::new(),
rollbacks: Vec::new(),
name: name.to_string(),
}
}
/// Adds an operation to the transaction with its corresponding rollback
2025-03-19 00:44:27 +01:00
pub fn add_operation<F, R>(&mut self, operation: F, rollback: R)
where
F: Future<Output = Result<(), DomainError>> + Send + 'static,
R: Future<Output = Result<(), DomainError>> + Send + 'static,
{
self.operations.push(Box::new(move || Box::pin(operation)));
self.rollbacks.push(Box::new(move || Box::pin(rollback)));
}
/// Adds an operation without rollback (for cleanup or logging)
2025-03-19 00:44:27 +01:00
pub fn add_finalizer<F>(&mut self, finalizer: F)
where
F: Future<Output = Result<(), DomainError>> + Send + 'static,
{
// The rollback is a no-op
2025-03-19 00:44:27 +01:00
let noop = async { Ok(()) };
self.operations.push(Box::new(move || Box::pin(finalizer)));
self.rollbacks.push(Box::new(move || Box::pin(noop)));
}
/// Executes the transaction by applying all operations in order
/// If any fails, executes rollbacks in reverse order
2025-03-19 00:44:27 +01:00
pub async fn commit(mut self) -> Result<(), DomainError> {
tracing::debug!("Starting transaction: {}", self.name);
2025-03-19 00:44:27 +01:00
let mut completed_ops = Vec::new();
// Extract operations to avoid ownership issues
2025-03-19 00:44:27 +01:00
let operations = std::mem::take(&mut self.operations);
let transaction_name = self.name.clone();
// Execute operations
2025-03-19 00:44:27 +01:00
for (i, op) in operations.into_iter().enumerate() {
match op().await {
Ok(()) => {
completed_ops.push(i);
tracing::trace!("Operation {} completed in transaction: {}", i, transaction_name);
2025-03-19 00:44:27 +01:00
}
Err(e) => {
tracing::error!("Error in operation {} of transaction {}: {}", i, transaction_name, e);
2025-03-19 00:44:27 +01:00
// Execute rollbacks for completed operations in reverse order
2025-03-19 00:44:27 +01:00
self.rollback(completed_ops).await?;
return Err(DomainError::new(
ErrorKind::InternalError,
"Transaction",
format!("Transaction '{}' failed: {}", transaction_name, e)
2025-03-19 00:44:27 +01:00
).with_source(e));
}
}
}
tracing::debug!("Transaction completed successfully: {}", transaction_name);
2025-03-19 00:44:27 +01:00
Ok(())
}
/// Executes rollbacks for completed operations
2025-03-19 00:44:27 +01:00
async fn rollback(mut self, completed_ops: Vec<usize>) -> Result<(), DomainError> {
tracing::warn!("Starting rollback for transaction: {}", self.name);
2025-03-19 00:44:27 +01:00
let mut rollback_errors = Vec::new();
// Extract rollbacks to avoid ownership issues
2025-03-19 00:44:27 +01:00
let mut rollbacks = Vec::new();
std::mem::swap(&mut rollbacks, &mut self.rollbacks);
// Execute rollbacks in reverse order
2025-03-19 00:44:27 +01:00
for i in completed_ops.into_iter().rev() {
if i < rollbacks.len() {
// Take ownership of the rollback (get a mutable reference)
2025-03-19 00:44:27 +01:00
if let Some(rb) = rollbacks.get_mut(i) {
// Swap with an empty function
2025-03-19 00:44:27 +01:00
let rollback = std::mem::replace(rb, Box::new(|| Box::pin(async { Ok(()) })));
if let Err(e) = rollback().await {
tracing::error!("Error in rollback of operation {} in transaction {}: {}",
2025-03-19 00:44:27 +01:00
i, self.name, e);
rollback_errors.push(e);
}
}
}
}
// If there were errors during rollback, report them
2025-03-19 00:44:27 +01:00
if !rollback_errors.is_empty() {
tracing::error!("Errors during transaction rollback {}: {} errors",
2025-03-19 00:44:27 +01:00
self.name, rollback_errors.len());
return Err(DomainError::new(
ErrorKind::InternalError,
"Transaction",
format!("Errors during transaction '{}' rollback: {} errors",
2025-03-19 00:44:27 +01:00
self.name, rollback_errors.len())
));
}
tracing::info!("Transaction rollback completed: {}", self.name);
2025-03-19 00:44:27 +01:00
Ok(())
}
}