1use crate::consensus::transport::NetworkTransport;
44use crate::distributed::coordinator::{CommitProtocol, CoordinatorConfig, TransactionCoordinator};
45use crate::distributed::deadlock::{DeadlockDetectorConfig, DistributedDeadlockDetector};
46use crate::distributed::replication::{ReplicationConfig, ReplicationManager};
47use crate::distributed::saga::{SagaConfig, SagaOrchestrator};
48use crate::error::{Result, TdbError};
49use chrono::Utc;
50use parking_lot::{Mutex, RwLock};
51use serde::{Deserialize, Serialize};
52use std::collections::HashMap;
53use std::sync::Arc;
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct DistributedConfig {
58 pub coordinator_config: CoordinatorConfig,
60 pub deadlock_config: DeadlockDetectorConfig,
62 pub replication_config: ReplicationConfig,
64 pub saga_config: SagaConfig,
66 pub default_protocol: CommitProtocol,
68 pub enable_deadlock_detection: bool,
70 pub enable_replication: bool,
72}
73
74impl Default for DistributedConfig {
75 fn default() -> Self {
76 Self {
77 coordinator_config: CoordinatorConfig::default(),
78 deadlock_config: DeadlockDetectorConfig::default(),
79 replication_config: ReplicationConfig::default(),
80 saga_config: SagaConfig::default(),
81 default_protocol: CommitProtocol::TwoPhase,
82 enable_deadlock_detection: true,
83 enable_replication: true,
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct DistributedTransaction {
91 pub txn_id: String,
93 pub protocol: CommitProtocol,
95 pub nodes: Vec<String>,
97}
98
99pub struct DistributedTdbStore {
104 node_id: String,
106 config: DistributedConfig,
108 coordinator: Arc<Mutex<TransactionCoordinator>>,
110 deadlock_detector: Option<Arc<Mutex<DistributedDeadlockDetector>>>,
112 replication_manager: Option<Arc<Mutex<ReplicationManager>>>,
114 active_transactions: Arc<RwLock<HashMap<String, DistributedTransaction>>>,
116 stats: Arc<Mutex<DistributedStoreStats>>,
118}
119
120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122pub struct DistributedStoreStats {
123 pub total_distributed_txns: u64,
125 pub successful_distributed_txns: u64,
127 pub failed_distributed_txns: u64,
129 pub deadlocks_resolved: u64,
131 pub replications_performed: u64,
133 pub avg_txn_latency_ms: f64,
135 total_latency_ms: f64,
137}
138
139impl DistributedTdbStore {
140 pub fn new(node_id: String, config: DistributedConfig) -> Self {
153 let coordinator = Arc::new(Mutex::new(TransactionCoordinator::new(
154 node_id.clone(),
155 config.coordinator_config.clone(),
156 )));
157
158 let deadlock_detector = if config.enable_deadlock_detection {
159 Some(Arc::new(Mutex::new(DistributedDeadlockDetector::new(
160 format!("{}-deadlock-detector", node_id),
161 config.deadlock_config.clone(),
162 ))))
163 } else {
164 None
165 };
166
167 let replication_manager = if config.enable_replication {
168 Some(Arc::new(Mutex::new(ReplicationManager::new(
169 node_id.clone(),
170 config.replication_config.clone(),
171 ))))
172 } else {
173 None
174 };
175
176 Self {
177 node_id,
178 config,
179 coordinator,
180 deadlock_detector,
181 replication_manager,
182 active_transactions: Arc::new(RwLock::new(HashMap::new())),
183 stats: Arc::new(Mutex::new(DistributedStoreStats::default())),
184 }
185 }
186
187 pub fn with_transport(
193 node_id: String,
194 config: DistributedConfig,
195 transport: Arc<dyn NetworkTransport>,
196 ) -> Self {
197 let coordinator = Arc::new(Mutex::new(TransactionCoordinator::with_transport(
198 node_id.clone(),
199 config.coordinator_config.clone(),
200 transport.clone(),
201 )));
202
203 let deadlock_detector = if config.enable_deadlock_detection {
204 Some(Arc::new(Mutex::new(DistributedDeadlockDetector::new(
205 format!("{}-deadlock-detector", node_id),
206 config.deadlock_config.clone(),
207 ))))
208 } else {
209 None
210 };
211
212 let replication_manager = if config.enable_replication {
213 Some(Arc::new(Mutex::new(ReplicationManager::with_transport(
214 node_id.clone(),
215 config.replication_config.clone(),
216 transport,
217 ))))
218 } else {
219 None
220 };
221
222 Self {
223 node_id,
224 config,
225 coordinator,
226 deadlock_detector,
227 replication_manager,
228 active_transactions: Arc::new(RwLock::new(HashMap::new())),
229 stats: Arc::new(Mutex::new(DistributedStoreStats::default())),
230 }
231 }
232
233 #[allow(clippy::await_holding_lock)]
235 pub async fn register_node(&mut self, node_id: &str, endpoint: &str) -> Result<()> {
236 {
238 self.coordinator
239 .lock()
240 .register_participant(node_id.to_string(), endpoint.to_string())
241 .await?;
242 }
243
244 if let Some(ref detector) = self.deadlock_detector {
246 detector.lock().register_node(node_id.to_string()).await?;
247 }
248
249 if let Some(ref manager) = self.replication_manager {
251 manager
252 .lock()
253 .add_replica(node_id.to_string(), endpoint.to_string())
254 .await?;
255 }
256
257 Ok(())
258 }
259
260 pub async fn begin_distributed_transaction(&mut self) -> Result<String> {
262 self.begin_distributed_transaction_with_protocol(self.config.default_protocol)
263 .await
264 }
265
266 #[allow(clippy::await_holding_lock)]
268 pub async fn begin_distributed_transaction_with_protocol(
269 &mut self,
270 protocol: CommitProtocol,
271 ) -> Result<String> {
272 let txn_id = self.coordinator.lock().begin_transaction(protocol).await?;
273
274 let metadata = {
275 let coordinator = self.coordinator.lock();
276 coordinator
277 .get_transaction(&txn_id)
278 .ok_or_else(|| TdbError::Other("Transaction not found".to_string()))?
279 };
280
281 let txn = DistributedTransaction {
282 txn_id: txn_id.clone(),
283 protocol,
284 nodes: metadata.participants.clone(),
285 };
286
287 self.active_transactions.write().insert(txn_id.clone(), txn);
288
289 let mut stats = self.stats.lock();
290 stats.total_distributed_txns += 1;
291
292 Ok(txn_id)
293 }
294
295 #[allow(clippy::await_holding_lock)]
297 pub async fn commit_distributed_transaction(&mut self, txn_id: &str) -> Result<bool> {
298 let start = std::time::Instant::now();
299
300 if let Some(ref detector) = self.deadlock_detector {
302 let deadlocks = detector.lock().detect_deadlocks().await?;
303
304 if !deadlocks.is_empty() {
305 for deadlock in &deadlocks {
307 if let Some(ref victim) = deadlock.victim {
308 detector.lock().abort_victim(victim).await?;
309
310 let mut stats = self.stats.lock();
311 stats.deadlocks_resolved += 1;
312 }
313 }
314 }
315 }
316
317 let result = self.coordinator.lock().commit_transaction(txn_id).await?;
319
320 if result {
322 if let Some(ref manager) = self.replication_manager {
323 manager
326 .lock()
327 .record_change(txn_id.to_string(), txn_id.as_bytes().to_vec())
328 .await?;
329 let mut stats = self.stats.lock();
330 stats.replications_performed += 1;
331 }
332 }
333
334 self.active_transactions.write().remove(txn_id);
336
337 let latency = start.elapsed().as_millis() as f64;
339 let mut stats = self.stats.lock();
340
341 if result {
342 stats.successful_distributed_txns += 1;
343 } else {
344 stats.failed_distributed_txns += 1;
345 }
346
347 stats.total_latency_ms += latency;
348 stats.avg_txn_latency_ms = stats.total_latency_ms / stats.total_distributed_txns as f64;
349
350 Ok(result)
351 }
352
353 #[allow(clippy::await_holding_lock)]
355 pub async fn abort_distributed_transaction(&mut self, txn_id: &str) -> Result<()> {
356 self.coordinator.lock().abort_transaction(txn_id).await?;
357
358 self.active_transactions.write().remove(txn_id);
359
360 let mut stats = self.stats.lock();
361 stats.failed_distributed_txns += 1;
362
363 Ok(())
364 }
365
366 #[allow(clippy::await_holding_lock)]
368 pub async fn execute_saga(&mut self, mut saga: SagaOrchestrator) -> Result<bool> {
369 let result = saga.execute().await?;
372
373 if let Some(ref manager) = self.replication_manager {
375 let saga_key = Utc::now().timestamp_millis().to_string();
376 let change_data = if result {
377 format!("saga:committed:{}", saga_key).into_bytes()
378 } else {
379 format!("saga:compensated:{}", saga_key).into_bytes()
380 };
381 manager.lock().record_change(saga_key, change_data).await?;
382 }
383
384 {
386 let mut stats = self.stats.lock();
387 if result {
388 stats.successful_distributed_txns += 1;
389 } else {
390 stats.failed_distributed_txns += 1;
391 }
392 stats.total_distributed_txns += 1;
393 }
394
395 Ok(result)
396 }
397
398 pub fn node_id(&self) -> &str {
400 &self.node_id
401 }
402
403 pub fn active_transaction_count(&self) -> usize {
405 self.active_transactions.read().len()
406 }
407
408 pub fn stats(&self) -> DistributedStoreStats {
410 self.stats.lock().clone()
411 }
412
413 pub fn coordinator_stats(&self) -> crate::distributed::coordinator::CoordinatorStats {
415 self.coordinator.lock().stats()
416 }
417
418 pub fn replication_stats(&self) -> Option<crate::distributed::replication::ReplicationStats> {
420 self.replication_manager.as_ref().map(|m| m.lock().stats())
421 }
422
423 pub async fn check_health(&self) -> Result<HealthStatus> {
425 let coordinator_health = self.coordinator.lock().healthy_participant_count() > 0;
426
427 let replication_health = if let Some(ref manager) = self.replication_manager {
428 manager.lock().healthy_replica_count() > 0
429 } else {
430 true
431 };
432
433 let overall_health = coordinator_health && replication_health;
434
435 Ok(HealthStatus {
436 healthy: overall_health,
437 coordinator_nodes: self.coordinator.lock().participant_count(),
438 healthy_coordinators: self.coordinator.lock().healthy_participant_count(),
439 replica_count: self
440 .replication_manager
441 .as_ref()
442 .map(|m| m.lock().replica_count())
443 .unwrap_or(0),
444 healthy_replicas: self
445 .replication_manager
446 .as_ref()
447 .map(|m| m.lock().healthy_replica_count())
448 .unwrap_or(0),
449 active_transactions: self.active_transaction_count(),
450 })
451 }
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct HealthStatus {
457 pub healthy: bool,
459 pub coordinator_nodes: usize,
461 pub healthy_coordinators: usize,
463 pub replica_count: usize,
465 pub healthy_replicas: usize,
467 pub active_transactions: usize,
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[tokio::test]
476 #[ignore = "Distributed tests hang - needs network mock or investigation"]
477 async fn test_distributed_store_creation() {
478 let config = DistributedConfig::default();
479 let store = DistributedTdbStore::new("node1".to_string(), config);
480
481 assert_eq!(store.node_id(), "node1");
482 assert_eq!(store.active_transaction_count(), 0);
483 }
484
485 #[tokio::test]
486 #[ignore = "Distributed tests hang - needs network mock or investigation"]
487 async fn test_register_node() {
488 let config = DistributedConfig::default();
489 let mut store = DistributedTdbStore::new("node1".to_string(), config);
490
491 store
492 .register_node("node2", "http://node2:8080")
493 .await
494 .unwrap();
495
496 assert!(store.coordinator.lock().participant_count() > 0);
498 }
499
500 #[tokio::test]
501 #[ignore = "Distributed tests hang - needs network mock or investigation"]
502 async fn test_begin_distributed_transaction() {
503 let config = DistributedConfig::default();
504 let mut store = DistributedTdbStore::new("node1".to_string(), config);
505
506 store
507 .register_node("node2", "http://node2:8080")
508 .await
509 .unwrap();
510
511 let txn_id = store.begin_distributed_transaction().await.unwrap();
512
513 assert!(!txn_id.is_empty());
514 assert_eq!(store.active_transaction_count(), 1);
515 }
516
517 #[tokio::test]
518 #[ignore = "Distributed tests hang - needs network mock or investigation"]
519 async fn test_commit_distributed_transaction() {
520 let config = DistributedConfig::default();
521 let mut store = DistributedTdbStore::new("node1".to_string(), config);
522
523 store
524 .register_node("node2", "http://node2:8080")
525 .await
526 .unwrap();
527
528 let txn_id = store.begin_distributed_transaction().await.unwrap();
529
530 let result = store.commit_distributed_transaction(&txn_id).await.unwrap();
531
532 assert!(result);
533 assert_eq!(store.active_transaction_count(), 0);
534
535 let stats = store.stats();
536 assert_eq!(stats.successful_distributed_txns, 1);
537 }
538
539 #[tokio::test]
540 #[ignore = "Distributed tests hang - needs network mock or investigation"]
541 async fn test_abort_distributed_transaction() {
542 let config = DistributedConfig::default();
543 let mut store = DistributedTdbStore::new("node1".to_string(), config);
544
545 store
546 .register_node("node2", "http://node2:8080")
547 .await
548 .unwrap();
549
550 let txn_id = store.begin_distributed_transaction().await.unwrap();
551
552 store.abort_distributed_transaction(&txn_id).await.unwrap();
553
554 assert_eq!(store.active_transaction_count(), 0);
555
556 let stats = store.stats();
557 assert_eq!(stats.failed_distributed_txns, 1);
558 }
559
560 #[tokio::test]
561 #[ignore = "Distributed tests hang - needs network mock or investigation"]
562 async fn test_health_check() {
563 let config = DistributedConfig::default();
564 let store = DistributedTdbStore::new("node1".to_string(), config);
565
566 let health = store.check_health().await.unwrap();
567
568 assert_eq!(health.active_transactions, 0);
569 }
570
571 #[tokio::test]
572 #[ignore = "Distributed tests hang - needs network mock or investigation"]
573 async fn test_statistics() {
574 let config = DistributedConfig::default();
575 let store = DistributedTdbStore::new("node1".to_string(), config);
576
577 let stats = store.stats();
578 assert_eq!(stats.total_distributed_txns, 0);
579
580 let coord_stats = store.coordinator_stats();
581 assert_eq!(coord_stats.total_transactions, 0);
582 }
583
584 #[tokio::test]
585 #[ignore = "Distributed tests hang - needs network mock or investigation"]
586 async fn test_protocol_selection() {
587 let config = DistributedConfig::default();
588 let mut store = DistributedTdbStore::new("node1".to_string(), config);
589
590 store
591 .register_node("node2", "http://node2:8080")
592 .await
593 .unwrap();
594
595 let txn_id = store
597 .begin_distributed_transaction_with_protocol(CommitProtocol::ThreePhase)
598 .await
599 .unwrap();
600
601 let txn = store.active_transactions.read().get(&txn_id).cloned();
602 assert!(txn.is_some());
603 assert_eq!(txn.unwrap().protocol, CommitProtocol::ThreePhase);
604 }
605
606 #[tokio::test]
607 #[ignore = "Distributed tests hang - needs network mock or investigation"]
608 async fn test_multiple_transactions() {
609 let config = DistributedConfig::default();
610 let mut store = DistributedTdbStore::new("node1".to_string(), config);
611
612 store
613 .register_node("node2", "http://node2:8080")
614 .await
615 .unwrap();
616
617 let txn1 = store.begin_distributed_transaction().await.unwrap();
618 let txn2 = store.begin_distributed_transaction().await.unwrap();
619
620 assert_eq!(store.active_transaction_count(), 2);
621
622 store.commit_distributed_transaction(&txn1).await.unwrap();
623 assert_eq!(store.active_transaction_count(), 1);
624
625 store.abort_distributed_transaction(&txn2).await.unwrap();
626 assert_eq!(store.active_transaction_count(), 0);
627 }
628
629 #[tokio::test]
630 async fn test_commit_triggers_replication() {
631 use crate::consensus::transport::LoopbackSimulationTransport;
632
633 let config = DistributedConfig {
634 enable_deadlock_detection: false,
635 ..Default::default()
636 };
637 let mut store = DistributedTdbStore::with_transport(
638 "node1".to_string(),
639 config,
640 LoopbackSimulationTransport::arc(),
641 );
642 store
643 .register_node("node2", "http://node2:8080")
644 .await
645 .unwrap();
646
647 let txn_id = store.begin_distributed_transaction().await.unwrap();
648 let result = store.commit_distributed_transaction(&txn_id).await.unwrap();
649 assert!(result);
650
651 let stats = store.stats();
652 assert_eq!(stats.replications_performed, 1);
653 }
654
655 #[tokio::test]
659 async fn test_commit_without_transport_fails_loudly() {
660 let config = DistributedConfig {
661 enable_deadlock_detection: false,
662 ..Default::default()
663 };
664 let mut store = DistributedTdbStore::new("node1".to_string(), config);
665 store
666 .register_node("node2", "http://node2:8080")
667 .await
668 .unwrap();
669
670 let txn_id = store.begin_distributed_transaction().await.unwrap();
671 let err = store
672 .commit_distributed_transaction(&txn_id)
673 .await
674 .expect_err("commit without a configured transport must fail, not fabricate success");
675 assert!(
676 matches!(err, TdbError::DistributedTransportNotConfigured { .. }),
677 "expected DistributedTransportNotConfigured, got {err:?}"
678 );
679 }
680
681 #[tokio::test]
682 async fn test_execute_saga_success_replicates() {
683 use crate::distributed::saga::{SagaConfig, SagaOrchestrator, SagaStep};
684 use std::sync::atomic::{AtomicU32, Ordering};
685 use std::sync::Arc as StdArc;
686
687 let config = DistributedConfig {
688 enable_deadlock_detection: false,
689 ..Default::default()
690 };
691 let mut store = DistributedTdbStore::new("node1".to_string(), config);
692 store
693 .register_node("node2", "http://node2:8080")
694 .await
695 .unwrap();
696
697 let mut saga = SagaOrchestrator::new("test-saga".to_string(), SagaConfig::default());
698 let counter = StdArc::new(AtomicU32::new(0));
699 let counter_clone = counter.clone();
700 saga.add_step(SagaStep {
701 name: "step-1".to_string(),
702 ..Default::default()
703 });
704 saga.register_step_callbacks(
705 "step-1",
706 move || {
707 counter_clone.fetch_add(1, Ordering::SeqCst);
708 Ok(())
709 },
710 || Ok(()),
711 );
712
713 let result = store.execute_saga(saga).await.unwrap();
714 assert!(result);
715 assert_eq!(counter.load(Ordering::SeqCst), 1);
716
717 let rep_stats = store.replication_stats();
719 assert!(rep_stats.is_some());
720 let rs = rep_stats.unwrap();
721 assert_eq!(rs.total_changes, 1);
722 }
723}