use crate::error::{Result, TdbError};
use anyhow::Context;
use chrono::{DateTime, Utc};
use parking_lot::{Mutex, RwLock};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TpcState {
Init,
Preparing,
Prepared,
Committing,
Aborting,
Committed,
Aborted,
TimedOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Participant {
pub node_id: String,
pub endpoint: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Vote {
Yes,
No,
Timeout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ParticipantState {
participant: Participant,
vote: Option<Vote>,
prepare_timestamp: Option<DateTime<Utc>>,
commit_timestamp: Option<DateTime<Utc>>,
}
pub struct TwoPhaseCoordinator {
txn_id: String,
state: Arc<RwLock<TpcState>>,
participants: Arc<Mutex<HashMap<String, ParticipantState>>>,
start_time: DateTime<Utc>,
prepare_timeout: Duration,
commit_timeout: Duration,
stats: Arc<Mutex<TpcCoordinatorStats>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TpcCoordinatorStats {
pub total_transactions: u64,
pub successful_commits: u64,
pub aborted_transactions: u64,
pub timed_out_transactions: u64,
pub avg_prepare_duration_ms: f64,
pub avg_commit_duration_ms: f64,
total_prepare_duration_ms: f64,
total_commit_duration_ms: f64,
}
impl TwoPhaseCoordinator {
pub fn new(txn_id: String) -> Self {
Self {
txn_id,
state: Arc::new(RwLock::new(TpcState::Init)),
participants: Arc::new(Mutex::new(HashMap::new())),
start_time: Utc::now(),
prepare_timeout: Duration::from_secs(30),
commit_timeout: Duration::from_secs(60),
stats: Arc::new(Mutex::new(TpcCoordinatorStats::default())),
}
}
pub fn add_participant(&mut self, participant: Participant) {
let mut participants = self.participants.lock();
participants.insert(
participant.node_id.clone(),
ParticipantState {
participant,
vote: None,
prepare_timestamp: None,
commit_timestamp: None,
},
);
}
pub fn set_prepare_timeout(&mut self, timeout: Duration) {
self.prepare_timeout = timeout;
}
pub fn set_commit_timeout(&mut self, timeout: Duration) {
self.commit_timeout = timeout;
}
pub async fn commit(&mut self) -> Result<bool> {
{
let mut stats = self.stats.lock();
stats.total_transactions += 1;
}
let prepare_start = Utc::now();
let prepare_result = self.prepare_phase().await?;
let prepare_duration = (Utc::now() - prepare_start).num_milliseconds() as f64;
{
let mut stats = self.stats.lock();
stats.total_prepare_duration_ms += prepare_duration;
stats.avg_prepare_duration_ms =
stats.total_prepare_duration_ms / stats.total_transactions as f64;
}
if !prepare_result {
self.abort_phase().await?;
{
let mut stats = self.stats.lock();
stats.aborted_transactions += 1;
}
return Ok(false);
}
let commit_start = Utc::now();
let commit_result = self.commit_phase().await?;
let commit_duration = (Utc::now() - commit_start).num_milliseconds() as f64;
{
let mut stats = self.stats.lock();
stats.total_commit_duration_ms += commit_duration;
stats.avg_commit_duration_ms =
stats.total_commit_duration_ms / stats.total_transactions as f64;
if commit_result {
stats.successful_commits += 1;
} else {
stats.aborted_transactions += 1;
}
}
Ok(commit_result)
}
async fn prepare_phase(&mut self) -> Result<bool> {
*self.state.write() = TpcState::Preparing;
let participants = self.participants.lock().clone();
let prepare_results = self.send_prepare_messages(&participants).await?;
let all_yes = prepare_results.iter().all(|(_, vote)| *vote == Vote::Yes);
if all_yes {
*self.state.write() = TpcState::Prepared;
}
Ok(all_yes)
}
async fn send_prepare_messages(
&self,
participants: &HashMap<String, ParticipantState>,
) -> Result<HashMap<String, Vote>> {
let mut votes = HashMap::new();
for (node_id, _state) in participants.iter() {
let vote = self.request_prepare_vote(node_id).await?;
votes.insert(node_id.clone(), vote);
let mut participants = self.participants.lock();
if let Some(pstate) = participants.get_mut(node_id) {
pstate.vote = Some(vote);
pstate.prepare_timestamp = Some(Utc::now());
}
}
Ok(votes)
}
async fn request_prepare_vote(&self, _node_id: &str) -> Result<Vote> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(Vote::Yes)
}
async fn commit_phase(&mut self) -> Result<bool> {
*self.state.write() = TpcState::Committing;
let participants = self.participants.lock().clone();
self.send_commit_messages(&participants).await?;
*self.state.write() = TpcState::Committed;
Ok(true)
}
async fn send_commit_messages(
&self,
participants: &HashMap<String, ParticipantState>,
) -> Result<()> {
for (node_id, _state) in participants.iter() {
self.send_commit_message(node_id).await?;
let mut participants = self.participants.lock();
if let Some(pstate) = participants.get_mut(node_id) {
pstate.commit_timestamp = Some(Utc::now());
}
}
Ok(())
}
async fn send_commit_message(&self, _node_id: &str) -> Result<()> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(())
}
async fn abort_phase(&mut self) -> Result<()> {
*self.state.write() = TpcState::Aborting;
let participants = self.participants.lock().clone();
self.send_abort_messages(&participants).await?;
*self.state.write() = TpcState::Aborted;
Ok(())
}
async fn send_abort_messages(
&self,
participants: &HashMap<String, ParticipantState>,
) -> Result<()> {
for (node_id, _state) in participants.iter() {
self.send_abort_message(node_id).await?;
}
Ok(())
}
async fn send_abort_message(&self, _node_id: &str) -> Result<()> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(())
}
pub fn state(&self) -> TpcState {
*self.state.read()
}
pub fn txn_id(&self) -> &str {
&self.txn_id
}
pub fn stats(&self) -> TpcCoordinatorStats {
self.stats.lock().clone()
}
pub fn participant_count(&self) -> usize {
self.participants.lock().len()
}
pub fn get_votes(&self) -> HashMap<String, Option<Vote>> {
self.participants
.lock()
.iter()
.map(|(node_id, state)| (node_id.clone(), state.vote))
.collect()
}
}
pub struct TwoPhaseParticipant {
node_id: String,
state: Arc<RwLock<TpcState>>,
active_txns: Arc<Mutex<HashSet<String>>>,
stats: Arc<Mutex<TpcParticipantStats>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TpcParticipantStats {
pub total_prepare_requests: u64,
pub total_commits: u64,
pub total_aborts: u64,
pub yes_votes: u64,
pub no_votes: u64,
}
impl TwoPhaseParticipant {
pub fn new(node_id: String) -> Self {
Self {
node_id,
state: Arc::new(RwLock::new(TpcState::Init)),
active_txns: Arc::new(Mutex::new(HashSet::new())),
stats: Arc::new(Mutex::new(TpcParticipantStats::default())),
}
}
pub async fn handle_prepare(&self, txn_id: String) -> Result<Vote> {
{
let mut stats = self.stats.lock();
stats.total_prepare_requests += 1;
}
*self.state.write() = TpcState::Preparing;
let can_commit = self.can_commit(&txn_id).await?;
let vote = if can_commit {
*self.state.write() = TpcState::Prepared;
self.active_txns.lock().insert(txn_id.clone());
let mut stats = self.stats.lock();
stats.yes_votes += 1;
drop(stats);
Vote::Yes
} else {
let mut stats = self.stats.lock();
stats.no_votes += 1;
drop(stats);
Vote::No
};
Ok(vote)
}
async fn can_commit(&self, _txn_id: &str) -> Result<bool> {
tokio::time::sleep(Duration::from_millis(5)).await;
Ok(true)
}
pub async fn handle_commit(&self, txn_id: String) -> Result<()> {
*self.state.write() = TpcState::Committing;
self.execute_commit(&txn_id).await?;
*self.state.write() = TpcState::Committed;
self.active_txns.lock().remove(&txn_id);
let mut stats = self.stats.lock();
stats.total_commits += 1;
Ok(())
}
async fn execute_commit(&self, _txn_id: &str) -> Result<()> {
tokio::time::sleep(Duration::from_millis(5)).await;
Ok(())
}
pub async fn handle_abort(&self, txn_id: String) -> Result<()> {
*self.state.write() = TpcState::Aborting;
self.execute_abort(&txn_id).await?;
*self.state.write() = TpcState::Aborted;
self.active_txns.lock().remove(&txn_id);
let mut stats = self.stats.lock();
stats.total_aborts += 1;
Ok(())
}
async fn execute_abort(&self, _txn_id: &str) -> Result<()> {
tokio::time::sleep(Duration::from_millis(5)).await;
Ok(())
}
pub fn state(&self) -> TpcState {
*self.state.read()
}
pub fn node_id(&self) -> &str {
&self.node_id
}
pub fn stats(&self) -> TpcParticipantStats {
self.stats.lock().clone()
}
pub fn active_txn_count(&self) -> usize {
self.active_txns.lock().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_2pc_coordinator_creation() {
let coordinator = TwoPhaseCoordinator::new("txn-001".to_string());
assert_eq!(coordinator.txn_id(), "txn-001");
assert_eq!(coordinator.state(), TpcState::Init);
assert_eq!(coordinator.participant_count(), 0);
}
#[tokio::test]
async fn test_2pc_add_participants() {
let mut coordinator = TwoPhaseCoordinator::new("txn-002".to_string());
coordinator.add_participant(Participant {
node_id: "node1".to_string(),
endpoint: "http://node1:8080".to_string(),
});
coordinator.add_participant(Participant {
node_id: "node2".to_string(),
endpoint: "http://node2:8080".to_string(),
});
assert_eq!(coordinator.participant_count(), 2);
}
#[tokio::test]
async fn test_2pc_successful_commit() {
let mut coordinator = TwoPhaseCoordinator::new("txn-003".to_string());
coordinator.add_participant(Participant {
node_id: "node1".to_string(),
endpoint: "http://node1:8080".to_string(),
});
coordinator.add_participant(Participant {
node_id: "node2".to_string(),
endpoint: "http://node2:8080".to_string(),
});
let result = coordinator.commit().await.unwrap();
assert!(result, "Transaction should commit successfully");
assert_eq!(coordinator.state(), TpcState::Committed);
let stats = coordinator.stats();
assert_eq!(stats.successful_commits, 1);
assert_eq!(stats.total_transactions, 1);
}
#[tokio::test]
async fn test_2pc_coordinator_stats() {
let mut coordinator = TwoPhaseCoordinator::new("txn-004".to_string());
coordinator.add_participant(Participant {
node_id: "node1".to_string(),
endpoint: "http://node1:8080".to_string(),
});
coordinator.commit().await.unwrap();
let stats = coordinator.stats();
assert_eq!(stats.total_transactions, 1);
assert!(stats.avg_prepare_duration_ms > 0.0);
assert!(stats.avg_commit_duration_ms > 0.0);
}
#[tokio::test]
async fn test_2pc_participant_creation() {
let participant = TwoPhaseParticipant::new("node1".to_string());
assert_eq!(participant.node_id(), "node1");
assert_eq!(participant.state(), TpcState::Init);
assert_eq!(participant.active_txn_count(), 0);
}
#[tokio::test]
async fn test_2pc_participant_prepare() {
let participant = TwoPhaseParticipant::new("node1".to_string());
let vote = participant
.handle_prepare("txn-001".to_string())
.await
.unwrap();
assert_eq!(vote, Vote::Yes);
assert_eq!(participant.state(), TpcState::Prepared);
assert_eq!(participant.active_txn_count(), 1);
let stats = participant.stats();
assert_eq!(stats.total_prepare_requests, 1);
assert_eq!(stats.yes_votes, 1);
}
#[tokio::test]
async fn test_2pc_participant_commit() {
let participant = TwoPhaseParticipant::new("node1".to_string());
participant
.handle_prepare("txn-001".to_string())
.await
.unwrap();
participant
.handle_commit("txn-001".to_string())
.await
.unwrap();
assert_eq!(participant.state(), TpcState::Committed);
assert_eq!(participant.active_txn_count(), 0);
let stats = participant.stats();
assert_eq!(stats.total_commits, 1);
}
#[tokio::test]
async fn test_2pc_participant_abort() {
let participant = TwoPhaseParticipant::new("node1".to_string());
participant
.handle_prepare("txn-001".to_string())
.await
.unwrap();
participant
.handle_abort("txn-001".to_string())
.await
.unwrap();
assert_eq!(participant.state(), TpcState::Aborted);
assert_eq!(participant.active_txn_count(), 0);
let stats = participant.stats();
assert_eq!(stats.total_aborts, 1);
}
#[tokio::test]
async fn test_2pc_timeout_configuration() {
let mut coordinator = TwoPhaseCoordinator::new("txn-005".to_string());
coordinator.set_prepare_timeout(Duration::from_secs(10));
coordinator.set_commit_timeout(Duration::from_secs(20));
assert_eq!(coordinator.prepare_timeout, Duration::from_secs(10));
assert_eq!(coordinator.commit_timeout, Duration::from_secs(20));
}
#[tokio::test]
async fn test_2pc_multiple_transactions() {
let participant = TwoPhaseParticipant::new("node1".to_string());
participant
.handle_prepare("txn-001".to_string())
.await
.unwrap();
participant
.handle_commit("txn-001".to_string())
.await
.unwrap();
participant
.handle_prepare("txn-002".to_string())
.await
.unwrap();
participant
.handle_commit("txn-002".to_string())
.await
.unwrap();
let stats = participant.stats();
assert_eq!(stats.total_commits, 2);
assert_eq!(stats.total_prepare_requests, 2);
assert_eq!(stats.yes_votes, 2);
}
#[tokio::test]
async fn test_2pc_get_votes() {
let mut coordinator = TwoPhaseCoordinator::new("txn-006".to_string());
coordinator.add_participant(Participant {
node_id: "node1".to_string(),
endpoint: "http://node1:8080".to_string(),
});
let votes_before = coordinator.get_votes();
assert_eq!(votes_before.get("node1").unwrap(), &None);
coordinator.commit().await.unwrap();
let votes_after = coordinator.get_votes();
assert_eq!(votes_after.get("node1").unwrap(), &Some(Vote::Yes));
}
}