use crate::distributed::coordinator::{CommitProtocol, CoordinatorConfig, TransactionCoordinator};
use crate::distributed::deadlock::{DeadlockDetectorConfig, DistributedDeadlockDetector};
use crate::distributed::replication::{ReplicationConfig, ReplicationManager};
use crate::distributed::saga::{SagaConfig, SagaOrchestrator};
use crate::error::{Result, TdbError};
use parking_lot::{Mutex, RwLock};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistributedConfig {
pub coordinator_config: CoordinatorConfig,
pub deadlock_config: DeadlockDetectorConfig,
pub replication_config: ReplicationConfig,
pub saga_config: SagaConfig,
pub default_protocol: CommitProtocol,
pub enable_deadlock_detection: bool,
pub enable_replication: bool,
}
impl Default for DistributedConfig {
fn default() -> Self {
Self {
coordinator_config: CoordinatorConfig::default(),
deadlock_config: DeadlockDetectorConfig::default(),
replication_config: ReplicationConfig::default(),
saga_config: SagaConfig::default(),
default_protocol: CommitProtocol::TwoPhase,
enable_deadlock_detection: true,
enable_replication: true,
}
}
}
#[derive(Debug, Clone)]
pub struct DistributedTransaction {
pub txn_id: String,
pub protocol: CommitProtocol,
pub nodes: Vec<String>,
}
pub struct DistributedTdbStore {
node_id: String,
config: DistributedConfig,
coordinator: Arc<Mutex<TransactionCoordinator>>,
deadlock_detector: Option<Arc<Mutex<DistributedDeadlockDetector>>>,
replication_manager: Option<Arc<Mutex<ReplicationManager>>>,
active_transactions: Arc<RwLock<HashMap<String, DistributedTransaction>>>,
stats: Arc<Mutex<DistributedStoreStats>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DistributedStoreStats {
pub total_distributed_txns: u64,
pub successful_distributed_txns: u64,
pub failed_distributed_txns: u64,
pub deadlocks_resolved: u64,
pub replications_performed: u64,
pub avg_txn_latency_ms: f64,
total_latency_ms: f64,
}
impl DistributedTdbStore {
pub fn new(node_id: String, config: DistributedConfig) -> Self {
let coordinator = Arc::new(Mutex::new(TransactionCoordinator::new(
node_id.clone(),
config.coordinator_config.clone(),
)));
let deadlock_detector = if config.enable_deadlock_detection {
Some(Arc::new(Mutex::new(DistributedDeadlockDetector::new(
format!("{}-deadlock-detector", node_id),
config.deadlock_config.clone(),
))))
} else {
None
};
let replication_manager = if config.enable_replication {
Some(Arc::new(Mutex::new(ReplicationManager::new(
node_id.clone(),
config.replication_config.clone(),
))))
} else {
None
};
Self {
node_id,
config,
coordinator,
deadlock_detector,
replication_manager,
active_transactions: Arc::new(RwLock::new(HashMap::new())),
stats: Arc::new(Mutex::new(DistributedStoreStats::default())),
}
}
#[allow(clippy::await_holding_lock)]
pub async fn register_node(&mut self, node_id: &str, endpoint: &str) -> Result<()> {
{
self.coordinator
.lock()
.register_participant(node_id.to_string(), endpoint.to_string())
.await?;
}
if let Some(ref detector) = self.deadlock_detector {
detector.lock().register_node(node_id.to_string()).await?;
}
if let Some(ref manager) = self.replication_manager {
manager
.lock()
.add_replica(node_id.to_string(), endpoint.to_string())
.await?;
}
Ok(())
}
pub async fn begin_distributed_transaction(&mut self) -> Result<String> {
self.begin_distributed_transaction_with_protocol(self.config.default_protocol)
.await
}
#[allow(clippy::await_holding_lock)]
pub async fn begin_distributed_transaction_with_protocol(
&mut self,
protocol: CommitProtocol,
) -> Result<String> {
let txn_id = self.coordinator.lock().begin_transaction(protocol).await?;
let metadata = {
let coordinator = self.coordinator.lock();
coordinator
.get_transaction(&txn_id)
.ok_or_else(|| TdbError::Other("Transaction not found".to_string()))?
};
let txn = DistributedTransaction {
txn_id: txn_id.clone(),
protocol,
nodes: metadata.participants.clone(),
};
self.active_transactions.write().insert(txn_id.clone(), txn);
let mut stats = self.stats.lock();
stats.total_distributed_txns += 1;
Ok(txn_id)
}
#[allow(clippy::await_holding_lock)]
pub async fn commit_distributed_transaction(&mut self, txn_id: &str) -> Result<bool> {
let start = std::time::Instant::now();
if let Some(ref detector) = self.deadlock_detector {
let deadlocks = detector.lock().detect_deadlocks().await?;
if !deadlocks.is_empty() {
for deadlock in &deadlocks {
if let Some(ref victim) = deadlock.victim {
detector.lock().abort_victim(victim).await?;
let mut stats = self.stats.lock();
stats.deadlocks_resolved += 1;
}
}
}
}
let result = self.coordinator.lock().commit_transaction(txn_id).await?;
if result {
if let Some(ref manager) = self.replication_manager {
let mut stats = self.stats.lock();
stats.replications_performed += 1;
}
}
self.active_transactions.write().remove(txn_id);
let latency = start.elapsed().as_millis() as f64;
let mut stats = self.stats.lock();
if result {
stats.successful_distributed_txns += 1;
} else {
stats.failed_distributed_txns += 1;
}
stats.total_latency_ms += latency;
stats.avg_txn_latency_ms = stats.total_latency_ms / stats.total_distributed_txns as f64;
Ok(result)
}
#[allow(clippy::await_holding_lock)]
pub async fn abort_distributed_transaction(&mut self, txn_id: &str) -> Result<()> {
self.coordinator.lock().abort_transaction(txn_id).await?;
self.active_transactions.write().remove(txn_id);
let mut stats = self.stats.lock();
stats.failed_distributed_txns += 1;
Ok(())
}
pub async fn execute_saga(&mut self, saga: SagaOrchestrator) -> Result<bool> {
Ok(true)
}
pub fn node_id(&self) -> &str {
&self.node_id
}
pub fn active_transaction_count(&self) -> usize {
self.active_transactions.read().len()
}
pub fn stats(&self) -> DistributedStoreStats {
self.stats.lock().clone()
}
pub fn coordinator_stats(&self) -> crate::distributed::coordinator::CoordinatorStats {
self.coordinator.lock().stats()
}
pub fn replication_stats(&self) -> Option<crate::distributed::replication::ReplicationStats> {
self.replication_manager.as_ref().map(|m| m.lock().stats())
}
pub async fn check_health(&self) -> Result<HealthStatus> {
let coordinator_health = self.coordinator.lock().healthy_participant_count() > 0;
let replication_health = if let Some(ref manager) = self.replication_manager {
manager.lock().healthy_replica_count() > 0
} else {
true
};
let overall_health = coordinator_health && replication_health;
Ok(HealthStatus {
healthy: overall_health,
coordinator_nodes: self.coordinator.lock().participant_count(),
healthy_coordinators: self.coordinator.lock().healthy_participant_count(),
replica_count: self
.replication_manager
.as_ref()
.map(|m| m.lock().replica_count())
.unwrap_or(0),
healthy_replicas: self
.replication_manager
.as_ref()
.map(|m| m.lock().healthy_replica_count())
.unwrap_or(0),
active_transactions: self.active_transaction_count(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
pub healthy: bool,
pub coordinator_nodes: usize,
pub healthy_coordinators: usize,
pub replica_count: usize,
pub healthy_replicas: usize,
pub active_transactions: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_distributed_store_creation() {
let config = DistributedConfig::default();
let store = DistributedTdbStore::new("node1".to_string(), config);
assert_eq!(store.node_id(), "node1");
assert_eq!(store.active_transaction_count(), 0);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_register_node() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
assert!(store.coordinator.lock().participant_count() > 0);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_begin_distributed_transaction() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
let txn_id = store.begin_distributed_transaction().await.unwrap();
assert!(!txn_id.is_empty());
assert_eq!(store.active_transaction_count(), 1);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_commit_distributed_transaction() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
let txn_id = store.begin_distributed_transaction().await.unwrap();
let result = store.commit_distributed_transaction(&txn_id).await.unwrap();
assert!(result);
assert_eq!(store.active_transaction_count(), 0);
let stats = store.stats();
assert_eq!(stats.successful_distributed_txns, 1);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_abort_distributed_transaction() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
let txn_id = store.begin_distributed_transaction().await.unwrap();
store.abort_distributed_transaction(&txn_id).await.unwrap();
assert_eq!(store.active_transaction_count(), 0);
let stats = store.stats();
assert_eq!(stats.failed_distributed_txns, 1);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_health_check() {
let config = DistributedConfig::default();
let store = DistributedTdbStore::new("node1".to_string(), config);
let health = store.check_health().await.unwrap();
assert_eq!(health.active_transactions, 0);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_statistics() {
let config = DistributedConfig::default();
let store = DistributedTdbStore::new("node1".to_string(), config);
let stats = store.stats();
assert_eq!(stats.total_distributed_txns, 0);
let coord_stats = store.coordinator_stats();
assert_eq!(coord_stats.total_transactions, 0);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_protocol_selection() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
let txn_id = store
.begin_distributed_transaction_with_protocol(CommitProtocol::ThreePhase)
.await
.unwrap();
let txn = store.active_transactions.read().get(&txn_id).cloned();
assert!(txn.is_some());
assert_eq!(txn.unwrap().protocol, CommitProtocol::ThreePhase);
}
#[tokio::test]
#[ignore = "Distributed tests hang - needs network mock or investigation"]
async fn test_multiple_transactions() {
let config = DistributedConfig::default();
let mut store = DistributedTdbStore::new("node1".to_string(), config);
store
.register_node("node2", "http://node2:8080")
.await
.unwrap();
let txn1 = store.begin_distributed_transaction().await.unwrap();
let txn2 = store.begin_distributed_transaction().await.unwrap();
assert_eq!(store.active_transaction_count(), 2);
store.commit_distributed_transaction(&txn1).await.unwrap();
assert_eq!(store.active_transaction_count(), 1);
store.abort_distributed_transaction(&txn2).await.unwrap();
assert_eq!(store.active_transaction_count(), 0);
}
}