1use crate::error::{Result, TdbError};
54use crate::transaction::two_phase_commit::Participant;
55use crate::transaction::txn_context::{Transaction, TransactionManager};
56use anyhow::Context;
57use chrono::{DateTime, Utc};
58use parking_lot::{Mutex, RwLock};
59use serde::{Deserialize, Serialize};
60use std::collections::{HashMap, HashSet};
61use std::sync::Arc;
62use std::time::Duration;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum TpcPhase {
67 Init,
69 CanCommit,
71 WaitingCanCommit,
73 PreCommit,
75 WaitingPreCommit,
77 DoCommit,
79 WaitingCommit,
81 Committed,
83 Aborting,
85 Aborted,
87 TimedOut,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93pub enum CanCommitResponse {
94 Yes,
96 No,
98 Timeout,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104struct ParticipantState {
105 participant: Participant,
106 can_commit_response: Option<CanCommitResponse>,
107 pre_commit_acked: bool,
108 commit_acked: bool,
109 can_commit_timestamp: Option<DateTime<Utc>>,
110 pre_commit_timestamp: Option<DateTime<Utc>>,
111 commit_timestamp: Option<DateTime<Utc>>,
112}
113
114pub struct ThreePhaseCoordinator {
119 txn_id: String,
121 phase: Arc<RwLock<TpcPhase>>,
123 participants: Arc<Mutex<HashMap<String, ParticipantState>>>,
125 start_time: DateTime<Utc>,
127 can_commit_timeout: Duration,
129 pre_commit_timeout: Duration,
131 do_commit_timeout: Duration,
133 stats: Arc<Mutex<ThreePhaseStats>>,
135}
136
137#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139pub struct ThreePhaseStats {
140 pub total_transactions: u64,
142 pub successful_commits: u64,
144 pub aborted_transactions: u64,
146 pub timed_out_transactions: u64,
148 pub avg_can_commit_duration_ms: f64,
150 pub avg_pre_commit_duration_ms: f64,
152 pub avg_do_commit_duration_ms: f64,
154 total_can_commit_duration_ms: f64,
156 total_pre_commit_duration_ms: f64,
157 total_do_commit_duration_ms: f64,
158}
159
160impl ThreePhaseCoordinator {
161 pub fn new(txn_id: String) -> Self {
163 Self {
164 txn_id,
165 phase: Arc::new(RwLock::new(TpcPhase::Init)),
166 participants: Arc::new(Mutex::new(HashMap::new())),
167 start_time: Utc::now(),
168 can_commit_timeout: Duration::from_secs(20),
169 pre_commit_timeout: Duration::from_secs(30),
170 do_commit_timeout: Duration::from_secs(60),
171 stats: Arc::new(Mutex::new(ThreePhaseStats::default())),
172 }
173 }
174
175 pub fn add_participant(&mut self, participant: Participant) {
177 let mut participants = self.participants.lock();
178 participants.insert(
179 participant.node_id.clone(),
180 ParticipantState {
181 participant,
182 can_commit_response: None,
183 pre_commit_acked: false,
184 commit_acked: false,
185 can_commit_timestamp: None,
186 pre_commit_timestamp: None,
187 commit_timestamp: None,
188 },
189 );
190 }
191
192 pub fn set_timeouts(
194 &mut self,
195 can_commit: Duration,
196 pre_commit: Duration,
197 do_commit: Duration,
198 ) {
199 self.can_commit_timeout = can_commit;
200 self.pre_commit_timeout = pre_commit;
201 self.do_commit_timeout = do_commit;
202 }
203
204 pub async fn commit(&mut self) -> Result<bool> {
212 {
213 let mut stats = self.stats.lock();
214 stats.total_transactions += 1;
215 }
216
217 let can_commit_start = Utc::now();
219 let can_commit_result = self.can_commit_phase().await?;
220 let can_commit_duration = (Utc::now() - can_commit_start).num_milliseconds() as f64;
221
222 {
223 let mut stats = self.stats.lock();
224 stats.total_can_commit_duration_ms += can_commit_duration;
225 stats.avg_can_commit_duration_ms =
226 stats.total_can_commit_duration_ms / stats.total_transactions as f64;
227 }
228
229 if !can_commit_result {
230 self.abort_phase().await?;
232 {
233 let mut stats = self.stats.lock();
234 stats.aborted_transactions += 1;
235 }
236 return Ok(false);
237 }
238
239 let pre_commit_start = Utc::now();
241 let pre_commit_result = self.pre_commit_phase().await?;
242 let pre_commit_duration = (Utc::now() - pre_commit_start).num_milliseconds() as f64;
243
244 {
245 let mut stats = self.stats.lock();
246 stats.total_pre_commit_duration_ms += pre_commit_duration;
247 stats.avg_pre_commit_duration_ms =
248 stats.total_pre_commit_duration_ms / stats.total_transactions as f64;
249 }
250
251 if !pre_commit_result {
252 self.abort_phase().await?;
254 {
255 let mut stats = self.stats.lock();
256 stats.aborted_transactions += 1;
257 }
258 return Ok(false);
259 }
260
261 let do_commit_start = Utc::now();
263 let do_commit_result = self.do_commit_phase().await?;
264 let do_commit_duration = (Utc::now() - do_commit_start).num_milliseconds() as f64;
265
266 {
267 let mut stats = self.stats.lock();
268 stats.total_do_commit_duration_ms += do_commit_duration;
269 stats.avg_do_commit_duration_ms =
270 stats.total_do_commit_duration_ms / stats.total_transactions as f64;
271
272 if do_commit_result {
273 stats.successful_commits += 1;
274 } else {
275 stats.aborted_transactions += 1;
276 }
277 }
278
279 Ok(do_commit_result)
280 }
281
282 async fn can_commit_phase(&mut self) -> Result<bool> {
286 *self.phase.write() = TpcPhase::CanCommit;
287
288 let participants = self.participants.lock().clone();
289 let responses = self.send_can_commit_messages(&participants).await?;
290
291 *self.phase.write() = TpcPhase::WaitingCanCommit;
292
293 let all_yes = responses
295 .iter()
296 .all(|(_, response)| *response == CanCommitResponse::Yes);
297
298 Ok(all_yes)
299 }
300
301 async fn send_can_commit_messages(
303 &self,
304 participants: &HashMap<String, ParticipantState>,
305 ) -> Result<HashMap<String, CanCommitResponse>> {
306 let mut responses = HashMap::new();
307
308 for node_id in participants.keys() {
309 let response = self.request_can_commit(node_id).await?;
310 responses.insert(node_id.clone(), response);
311
312 let mut participants = self.participants.lock();
314 if let Some(pstate) = participants.get_mut(node_id) {
315 pstate.can_commit_response = Some(response);
316 pstate.can_commit_timestamp = Some(Utc::now());
317 }
318 }
319
320 Ok(responses)
321 }
322
323 async fn request_can_commit(&self, _node_id: &str) -> Result<CanCommitResponse> {
325 tokio::time::sleep(Duration::from_millis(10)).await;
328 Ok(CanCommitResponse::Yes)
329 }
330
331 async fn pre_commit_phase(&mut self) -> Result<bool> {
335 *self.phase.write() = TpcPhase::PreCommit;
336
337 let participants = self.participants.lock().clone();
338 self.send_pre_commit_messages(&participants).await?;
339
340 *self.phase.write() = TpcPhase::WaitingPreCommit;
341 Ok(true)
342 }
343
344 async fn send_pre_commit_messages(
346 &self,
347 participants: &HashMap<String, ParticipantState>,
348 ) -> Result<()> {
349 for node_id in participants.keys() {
350 self.send_pre_commit_message(node_id).await?;
351
352 let mut participants = self.participants.lock();
354 if let Some(pstate) = participants.get_mut(node_id) {
355 pstate.pre_commit_acked = true;
356 pstate.pre_commit_timestamp = Some(Utc::now());
357 }
358 }
359
360 Ok(())
361 }
362
363 async fn send_pre_commit_message(&self, _node_id: &str) -> Result<()> {
365 tokio::time::sleep(Duration::from_millis(10)).await;
368 Ok(())
369 }
370
371 async fn do_commit_phase(&mut self) -> Result<bool> {
375 *self.phase.write() = TpcPhase::DoCommit;
376
377 let participants = self.participants.lock().clone();
378 self.send_do_commit_messages(&participants).await?;
379
380 *self.phase.write() = TpcPhase::Committed;
381 Ok(true)
382 }
383
384 async fn send_do_commit_messages(
386 &self,
387 participants: &HashMap<String, ParticipantState>,
388 ) -> Result<()> {
389 for node_id in participants.keys() {
390 self.send_do_commit_message(node_id).await?;
391
392 let mut participants = self.participants.lock();
394 if let Some(pstate) = participants.get_mut(node_id) {
395 pstate.commit_acked = true;
396 pstate.commit_timestamp = Some(Utc::now());
397 }
398 }
399
400 Ok(())
401 }
402
403 async fn send_do_commit_message(&self, _node_id: &str) -> Result<()> {
405 tokio::time::sleep(Duration::from_millis(10)).await;
408 Ok(())
409 }
410
411 async fn abort_phase(&mut self) -> Result<()> {
415 *self.phase.write() = TpcPhase::Aborting;
416
417 let participants = self.participants.lock().clone();
418 self.send_abort_messages(&participants).await?;
419
420 *self.phase.write() = TpcPhase::Aborted;
421 Ok(())
422 }
423
424 async fn send_abort_messages(
426 &self,
427 participants: &HashMap<String, ParticipantState>,
428 ) -> Result<()> {
429 for node_id in participants.keys() {
430 self.send_abort_message(node_id).await?;
431 }
432
433 Ok(())
434 }
435
436 async fn send_abort_message(&self, _node_id: &str) -> Result<()> {
438 tokio::time::sleep(Duration::from_millis(10)).await;
441 Ok(())
442 }
443
444 pub fn phase(&self) -> TpcPhase {
446 *self.phase.read()
447 }
448
449 pub fn txn_id(&self) -> &str {
451 &self.txn_id
452 }
453
454 pub fn stats(&self) -> ThreePhaseStats {
456 self.stats.lock().clone()
457 }
458
459 pub fn participant_count(&self) -> usize {
461 self.participants.lock().len()
462 }
463
464 pub fn get_can_commit_responses(&self) -> HashMap<String, Option<CanCommitResponse>> {
466 self.participants
467 .lock()
468 .iter()
469 .map(|(node_id, state)| (node_id.clone(), state.can_commit_response))
470 .collect()
471 }
472}
473
474pub struct ThreePhaseParticipant {
478 node_id: String,
480 phase: Arc<RwLock<TpcPhase>>,
482 active_txns: Arc<Mutex<HashSet<String>>>,
484 pre_committed_txns: Arc<Mutex<HashSet<String>>>,
486 stats: Arc<Mutex<ThreePhaseParticipantStats>>,
488 txn_manager: Option<Arc<TransactionManager>>,
490 local_txns: Arc<Mutex<HashMap<String, Transaction>>>,
492}
493
494#[derive(Debug, Clone, Default, Serialize, Deserialize)]
496pub struct ThreePhaseParticipantStats {
497 pub total_can_commit_requests: u64,
499 pub total_pre_commit_messages: u64,
501 pub total_commits: u64,
503 pub total_aborts: u64,
505 pub yes_responses: u64,
507 pub no_responses: u64,
509}
510
511impl ThreePhaseParticipant {
512 pub fn new(node_id: String) -> Self {
514 Self {
515 node_id,
516 phase: Arc::new(RwLock::new(TpcPhase::Init)),
517 active_txns: Arc::new(Mutex::new(HashSet::new())),
518 pre_committed_txns: Arc::new(Mutex::new(HashSet::new())),
519 stats: Arc::new(Mutex::new(ThreePhaseParticipantStats::default())),
520 txn_manager: None,
521 local_txns: Arc::new(Mutex::new(HashMap::new())),
522 }
523 }
524
525 pub fn with_transaction_manager(node_id: String, txn_manager: Arc<TransactionManager>) -> Self {
527 Self {
528 node_id,
529 phase: Arc::new(RwLock::new(TpcPhase::Init)),
530 active_txns: Arc::new(Mutex::new(HashSet::new())),
531 pre_committed_txns: Arc::new(Mutex::new(HashSet::new())),
532 stats: Arc::new(Mutex::new(ThreePhaseParticipantStats::default())),
533 txn_manager: Some(txn_manager),
534 local_txns: Arc::new(Mutex::new(HashMap::new())),
535 }
536 }
537
538 pub async fn handle_can_commit(&self, txn_id: String) -> Result<CanCommitResponse> {
540 {
541 let mut stats = self.stats.lock();
542 stats.total_can_commit_requests += 1;
543 }
544
545 *self.phase.write() = TpcPhase::CanCommit;
546
547 let can_commit = self.can_commit(&txn_id).await?;
549
550 let response = if can_commit {
551 self.active_txns.lock().insert(txn_id.clone());
552
553 let mut stats = self.stats.lock();
554 stats.yes_responses += 1;
555 drop(stats);
556
557 CanCommitResponse::Yes
558 } else {
559 let mut stats = self.stats.lock();
560 stats.no_responses += 1;
561 drop(stats);
562
563 CanCommitResponse::No
564 };
565
566 Ok(response)
567 }
568
569 async fn can_commit(&self, txn_id: &str) -> Result<bool> {
571 if let Some(tm) = &self.txn_manager {
572 let txn = tm.begin()?;
573 self.local_txns.lock().insert(txn_id.to_string(), txn);
574 }
575 Ok(true)
576 }
577
578 pub async fn handle_pre_commit(&self, txn_id: String) -> Result<()> {
580 {
581 let mut stats = self.stats.lock();
582 stats.total_pre_commit_messages += 1;
583 }
584
585 *self.phase.write() = TpcPhase::PreCommit;
586
587 self.pre_committed_txns.lock().insert(txn_id.clone());
589
590 self.execute_pre_commit(&txn_id).await?;
592
593 *self.phase.write() = TpcPhase::WaitingCommit;
594 Ok(())
595 }
596
597 async fn execute_pre_commit(&self, txn_id: &str) -> Result<()> {
599 let is_active = self
601 .local_txns
602 .lock()
603 .get(txn_id)
604 .map(|txn| txn.is_active())
605 .unwrap_or(true); if !is_active {
607 return Err(TdbError::Transaction(format!(
608 "Local transaction for {} is no longer active at pre-commit",
609 txn_id
610 )));
611 }
612 Ok(())
613 }
614
615 pub async fn handle_do_commit(&self, txn_id: String) -> Result<()> {
617 *self.phase.write() = TpcPhase::DoCommit;
618
619 self.execute_commit(&txn_id).await?;
621
622 *self.phase.write() = TpcPhase::Committed;
623 self.active_txns.lock().remove(&txn_id);
624 self.pre_committed_txns.lock().remove(&txn_id);
625
626 let mut stats = self.stats.lock();
627 stats.total_commits += 1;
628
629 Ok(())
630 }
631
632 async fn execute_commit(&self, txn_id: &str) -> Result<()> {
634 if let Some(txn) = self.local_txns.lock().remove(txn_id) {
635 txn.commit()
636 .map_err(|e| TdbError::Transaction(e.to_string()))?;
637 }
638 Ok(())
639 }
640
641 pub async fn handle_abort(&self, txn_id: String) -> Result<()> {
643 *self.phase.write() = TpcPhase::Aborting;
644
645 self.execute_abort(&txn_id).await?;
647
648 *self.phase.write() = TpcPhase::Aborted;
649 self.active_txns.lock().remove(&txn_id);
650 self.pre_committed_txns.lock().remove(&txn_id);
651
652 let mut stats = self.stats.lock();
653 stats.total_aborts += 1;
654
655 Ok(())
656 }
657
658 async fn execute_abort(&self, txn_id: &str) -> Result<()> {
660 if let Some(txn) = self.local_txns.lock().remove(txn_id) {
661 txn.abort()
662 .map_err(|e| TdbError::Transaction(e.to_string()))?;
663 }
664 Ok(())
665 }
666
667 pub async fn handle_coordinator_timeout(&self, txn_id: String) -> Result<bool> {
672 let is_pre_committed = self.pre_committed_txns.lock().contains(&txn_id);
674
675 if is_pre_committed {
676 self.handle_do_commit(txn_id).await?;
678 Ok(true)
679 } else {
680 self.handle_abort(txn_id).await?;
682 Ok(false)
683 }
684 }
685
686 pub fn phase(&self) -> TpcPhase {
688 *self.phase.read()
689 }
690
691 pub fn node_id(&self) -> &str {
693 &self.node_id
694 }
695
696 pub fn stats(&self) -> ThreePhaseParticipantStats {
698 self.stats.lock().clone()
699 }
700
701 pub fn active_txn_count(&self) -> usize {
703 self.active_txns.lock().len()
704 }
705
706 pub fn pre_committed_txn_count(&self) -> usize {
708 self.pre_committed_txns.lock().len()
709 }
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715
716 #[tokio::test]
717 async fn test_3pc_coordinator_creation() {
718 let coordinator = ThreePhaseCoordinator::new("txn-001".to_string());
719 assert_eq!(coordinator.txn_id(), "txn-001");
720 assert_eq!(coordinator.phase(), TpcPhase::Init);
721 assert_eq!(coordinator.participant_count(), 0);
722 }
723
724 #[tokio::test]
725 async fn test_3pc_successful_commit() {
726 let mut coordinator = ThreePhaseCoordinator::new("txn-002".to_string());
727
728 coordinator.add_participant(Participant {
729 node_id: "node1".to_string(),
730 endpoint: "http://node1:8080".to_string(),
731 });
732
733 coordinator.add_participant(Participant {
734 node_id: "node2".to_string(),
735 endpoint: "http://node2:8080".to_string(),
736 });
737
738 let result = coordinator.commit().await.unwrap();
739 assert!(result, "Transaction should commit successfully");
740 assert_eq!(coordinator.phase(), TpcPhase::Committed);
741
742 let stats = coordinator.stats();
743 assert_eq!(stats.successful_commits, 1);
744 assert_eq!(stats.total_transactions, 1);
745 }
746
747 #[tokio::test]
748 async fn test_3pc_coordinator_stats() {
749 let mut coordinator = ThreePhaseCoordinator::new("txn-003".to_string());
750
751 coordinator.add_participant(Participant {
752 node_id: "node1".to_string(),
753 endpoint: "http://node1:8080".to_string(),
754 });
755
756 coordinator.commit().await.unwrap();
757
758 let stats = coordinator.stats();
759 assert_eq!(stats.total_transactions, 1);
760 assert!(stats.avg_can_commit_duration_ms > 0.0);
761 assert!(stats.avg_pre_commit_duration_ms > 0.0);
762 assert!(stats.avg_do_commit_duration_ms > 0.0);
763 }
764
765 #[tokio::test]
766 async fn test_3pc_participant_creation() {
767 let participant = ThreePhaseParticipant::new("node1".to_string());
768 assert_eq!(participant.node_id(), "node1");
769 assert_eq!(participant.phase(), TpcPhase::Init);
770 assert_eq!(participant.active_txn_count(), 0);
771 }
772
773 #[tokio::test]
774 async fn test_3pc_participant_can_commit() {
775 let participant = ThreePhaseParticipant::new("node1".to_string());
776
777 let response = participant
778 .handle_can_commit("txn-001".to_string())
779 .await
780 .unwrap();
781 assert_eq!(response, CanCommitResponse::Yes);
782 assert_eq!(participant.active_txn_count(), 1);
783
784 let stats = participant.stats();
785 assert_eq!(stats.total_can_commit_requests, 1);
786 assert_eq!(stats.yes_responses, 1);
787 }
788
789 #[tokio::test]
790 async fn test_3pc_participant_pre_commit() {
791 let participant = ThreePhaseParticipant::new("node1".to_string());
792
793 participant
795 .handle_can_commit("txn-001".to_string())
796 .await
797 .unwrap();
798
799 participant
801 .handle_pre_commit("txn-001".to_string())
802 .await
803 .unwrap();
804
805 assert_eq!(participant.pre_committed_txn_count(), 1);
806
807 let stats = participant.stats();
808 assert_eq!(stats.total_pre_commit_messages, 1);
809 }
810
811 #[tokio::test]
812 async fn test_3pc_participant_commit() {
813 let participant = ThreePhaseParticipant::new("node1".to_string());
814
815 participant
817 .handle_can_commit("txn-001".to_string())
818 .await
819 .unwrap();
820 participant
821 .handle_pre_commit("txn-001".to_string())
822 .await
823 .unwrap();
824 participant
825 .handle_do_commit("txn-001".to_string())
826 .await
827 .unwrap();
828
829 assert_eq!(participant.phase(), TpcPhase::Committed);
830 assert_eq!(participant.active_txn_count(), 0);
831 assert_eq!(participant.pre_committed_txn_count(), 0);
832
833 let stats = participant.stats();
834 assert_eq!(stats.total_commits, 1);
835 }
836
837 #[tokio::test]
838 async fn test_3pc_participant_abort() {
839 let participant = ThreePhaseParticipant::new("node1".to_string());
840
841 participant
842 .handle_can_commit("txn-001".to_string())
843 .await
844 .unwrap();
845 participant
846 .handle_abort("txn-001".to_string())
847 .await
848 .unwrap();
849
850 assert_eq!(participant.phase(), TpcPhase::Aborted);
851 assert_eq!(participant.active_txn_count(), 0);
852
853 let stats = participant.stats();
854 assert_eq!(stats.total_aborts, 1);
855 }
856
857 #[tokio::test]
858 async fn test_3pc_coordinator_timeout() {
859 let mut coordinator = ThreePhaseCoordinator::new("txn-004".to_string());
860
861 coordinator.set_timeouts(
862 Duration::from_secs(5),
863 Duration::from_secs(10),
864 Duration::from_secs(15),
865 );
866
867 assert_eq!(coordinator.can_commit_timeout, Duration::from_secs(5));
868 assert_eq!(coordinator.pre_commit_timeout, Duration::from_secs(10));
869 assert_eq!(coordinator.do_commit_timeout, Duration::from_secs(15));
870 }
871
872 #[tokio::test]
873 async fn test_3pc_participant_coordinator_timeout_pre_committed() {
874 let participant = ThreePhaseParticipant::new("node1".to_string());
875
876 participant
878 .handle_can_commit("txn-001".to_string())
879 .await
880 .unwrap();
881 participant
882 .handle_pre_commit("txn-001".to_string())
883 .await
884 .unwrap();
885
886 let can_commit = participant
888 .handle_coordinator_timeout("txn-001".to_string())
889 .await
890 .unwrap();
891 assert!(
892 can_commit,
893 "Pre-committed transaction should commit on timeout"
894 );
895 assert_eq!(participant.phase(), TpcPhase::Committed);
896 }
897
898 #[tokio::test]
899 async fn test_3pc_participant_coordinator_timeout_not_pre_committed() {
900 let participant = ThreePhaseParticipant::new("node1".to_string());
901
902 participant
904 .handle_can_commit("txn-001".to_string())
905 .await
906 .unwrap();
907
908 let can_commit = participant
910 .handle_coordinator_timeout("txn-001".to_string())
911 .await
912 .unwrap();
913 assert!(
914 !can_commit,
915 "Non-pre-committed transaction should abort on timeout"
916 );
917 assert_eq!(participant.phase(), TpcPhase::Aborted);
918 }
919
920 #[tokio::test]
921 async fn test_3pc_multiple_transactions() {
922 let participant = ThreePhaseParticipant::new("node1".to_string());
923
924 participant
926 .handle_can_commit("txn-001".to_string())
927 .await
928 .unwrap();
929 participant
930 .handle_pre_commit("txn-001".to_string())
931 .await
932 .unwrap();
933 participant
934 .handle_do_commit("txn-001".to_string())
935 .await
936 .unwrap();
937
938 participant
940 .handle_can_commit("txn-002".to_string())
941 .await
942 .unwrap();
943 participant
944 .handle_pre_commit("txn-002".to_string())
945 .await
946 .unwrap();
947 participant
948 .handle_do_commit("txn-002".to_string())
949 .await
950 .unwrap();
951
952 let stats = participant.stats();
953 assert_eq!(stats.total_commits, 2);
954 assert_eq!(stats.total_can_commit_requests, 2);
955 assert_eq!(stats.total_pre_commit_messages, 2);
956 }
957
958 #[tokio::test]
959 async fn test_3pc_get_can_commit_responses() {
960 let mut coordinator = ThreePhaseCoordinator::new("txn-005".to_string());
961
962 coordinator.add_participant(Participant {
963 node_id: "node1".to_string(),
964 endpoint: "http://node1:8080".to_string(),
965 });
966
967 let responses_before = coordinator.get_can_commit_responses();
969 assert_eq!(responses_before.get("node1").unwrap(), &None);
970
971 coordinator.commit().await.unwrap();
973
974 let responses_after = coordinator.get_can_commit_responses();
975 assert_eq!(
976 responses_after.get("node1").unwrap(),
977 &Some(CanCommitResponse::Yes)
978 );
979 }
980
981 #[tokio::test]
982 async fn test_3pc_participant_with_txn_manager_full_commit() {
983 use crate::transaction::{lock_manager::LockManager, wal::WriteAheadLog};
984 let dir = std::env::temp_dir().join(format!(
985 "oxirs_3pc_commit_{}",
986 std::time::SystemTime::now()
987 .duration_since(std::time::UNIX_EPOCH)
988 .unwrap()
989 .subsec_nanos()
990 ));
991 std::fs::create_dir_all(&dir).unwrap();
992 let wal = Arc::new(WriteAheadLog::new(&dir).unwrap());
993 let lm = Arc::new(LockManager::new());
994 let tm = Arc::new(TransactionManager::new(Arc::clone(&wal), Arc::clone(&lm)));
995
996 let participant =
997 ThreePhaseParticipant::with_transaction_manager("node-3pc-1".to_string(), tm);
998
999 let resp = participant
1000 .handle_can_commit("dist-txn-001".to_string())
1001 .await
1002 .unwrap();
1003 assert_eq!(resp, CanCommitResponse::Yes);
1004
1005 participant
1006 .handle_pre_commit("dist-txn-001".to_string())
1007 .await
1008 .unwrap();
1009 participant
1010 .handle_do_commit("dist-txn-001".to_string())
1011 .await
1012 .unwrap();
1013
1014 assert_eq!(participant.phase(), TpcPhase::Committed);
1015 let stats = participant.stats();
1016 assert_eq!(stats.total_commits, 1);
1017 }
1018
1019 #[tokio::test]
1020 async fn test_3pc_participant_with_txn_manager_abort_at_can_commit() {
1021 use crate::transaction::{lock_manager::LockManager, wal::WriteAheadLog};
1022 let dir = std::env::temp_dir().join(format!(
1023 "oxirs_3pc_abort_{}",
1024 std::time::SystemTime::now()
1025 .duration_since(std::time::UNIX_EPOCH)
1026 .unwrap()
1027 .subsec_nanos()
1028 ));
1029 std::fs::create_dir_all(&dir).unwrap();
1030 let wal = Arc::new(WriteAheadLog::new(&dir).unwrap());
1031 let lm = Arc::new(LockManager::new());
1032 let tm = Arc::new(TransactionManager::new(Arc::clone(&wal), Arc::clone(&lm)));
1033
1034 let participant =
1035 ThreePhaseParticipant::with_transaction_manager("node-3pc-2".to_string(), tm);
1036
1037 participant
1038 .handle_can_commit("dist-txn-002".to_string())
1039 .await
1040 .unwrap();
1041 participant
1042 .handle_abort("dist-txn-002".to_string())
1043 .await
1044 .unwrap();
1045
1046 assert_eq!(participant.phase(), TpcPhase::Aborted);
1047 let stats = participant.stats();
1048 assert_eq!(stats.total_aborts, 1);
1049 }
1050
1051 #[tokio::test]
1052 async fn test_3pc_participant_with_txn_manager_coordinator_timeout_pre_committed() {
1053 use crate::transaction::{lock_manager::LockManager, wal::WriteAheadLog};
1054 let dir = std::env::temp_dir().join(format!(
1055 "oxirs_3pc_timeout_{}",
1056 std::time::SystemTime::now()
1057 .duration_since(std::time::UNIX_EPOCH)
1058 .unwrap()
1059 .subsec_nanos()
1060 ));
1061 std::fs::create_dir_all(&dir).unwrap();
1062 let wal = Arc::new(WriteAheadLog::new(&dir).unwrap());
1063 let lm = Arc::new(LockManager::new());
1064 let tm = Arc::new(TransactionManager::new(Arc::clone(&wal), Arc::clone(&lm)));
1065
1066 let participant =
1067 ThreePhaseParticipant::with_transaction_manager("node-3pc-3".to_string(), tm);
1068
1069 participant
1070 .handle_can_commit("dist-txn-003".to_string())
1071 .await
1072 .unwrap();
1073 participant
1074 .handle_pre_commit("dist-txn-003".to_string())
1075 .await
1076 .unwrap();
1077
1078 let committed = participant
1080 .handle_coordinator_timeout("dist-txn-003".to_string())
1081 .await
1082 .unwrap();
1083 assert!(
1084 committed,
1085 "Pre-committed txn should commit on coordinator timeout"
1086 );
1087 assert_eq!(participant.phase(), TpcPhase::Committed);
1088 }
1089}