use super::{ballot_leader_election::Ballot, messages::sequence_paxos::*, util::LeaderState};
#[cfg(feature = "logging")]
use crate::utils::logger::create_logger;
use crate::{
messages::Message,
storage::{
internal_storage::{InternalStorage, InternalStorageConfig},
Entry, Snapshot, StopSign, Storage,
},
util::{
FlexibleQuorum, LogSync, NodeId, Quorum, SequenceNumber, READ_ERROR_MSG, WRITE_ERROR_MSG,
},
ClusterConfig, CompactionErr, OmniPaxosConfig, ProposeErr,
};
#[cfg(feature = "logging")]
use slog::{debug, info, trace, warn, Logger};
use std::{fmt::Debug, vec};
pub mod follower;
pub mod leader;
pub(crate) struct SequencePaxos<T, B>
where
T: Entry,
B: Storage<T>,
{
pub(crate) internal_storage: InternalStorage<B, T>,
pid: NodeId,
peers: Vec<NodeId>, state: (Role, Phase),
buffered_proposals: Vec<T>,
buffered_stopsign: Option<StopSign>,
outgoing: Vec<Message<T>>,
leader_state: LeaderState<T>,
latest_accepted_meta: Option<(Ballot, usize)>,
current_seq_num: SequenceNumber,
cached_promise_message: Option<Promise<T>>,
#[cfg(feature = "logging")]
logger: Logger,
}
impl<T, B> SequencePaxos<T, B>
where
T: Entry,
B: Storage<T>,
{
pub(crate) fn with(config: SequencePaxosConfig, storage: B) -> Self {
let pid = config.pid;
let peers = config.peers;
let num_nodes = &peers.len() + 1;
let quorum = Quorum::with(config.flexible_quorum, num_nodes);
let max_peer_pid = peers.iter().max().unwrap();
let max_pid = *std::cmp::max(max_peer_pid, &pid) as usize;
let mut outgoing = Vec::with_capacity(config.buffer_size);
let (state, leader) = match storage
.get_promise()
.expect("storage error while trying to read promise")
{
Some(b) => {
let state = (Role::Follower, Phase::Recover);
for peer_pid in &peers {
let prepreq = PrepareReq { n: b };
outgoing.push(Message::SequencePaxos(PaxosMessage {
from: pid,
to: *peer_pid,
msg: PaxosMsg::PrepareReq(prepreq),
}));
}
(state, b)
}
None => ((Role::Follower, Phase::None), Ballot::default()),
};
let internal_storage_config = InternalStorageConfig {
batch_size: config.batch_size,
};
let mut paxos = SequencePaxos {
internal_storage: InternalStorage::with(
storage,
internal_storage_config,
#[cfg(feature = "unicache")]
pid,
),
pid,
peers,
state,
buffered_proposals: vec![],
buffered_stopsign: None,
outgoing,
leader_state: LeaderState::<T>::with(leader, max_pid, quorum),
latest_accepted_meta: None,
current_seq_num: SequenceNumber::default(),
cached_promise_message: None,
#[cfg(feature = "logging")]
logger: {
if let Some(logger) = config.custom_logger {
logger
} else {
let s = config
.logger_file_path
.unwrap_or_else(|| format!("logs/paxos_{}.log", pid));
create_logger(s.as_str())
}
},
};
paxos
.internal_storage
.set_promise(leader)
.expect(WRITE_ERROR_MSG);
#[cfg(feature = "logging")]
{
info!(paxos.logger, "Paxos component pid: {} created!", pid);
if let Quorum::Flexible(flex_quorum) = quorum {
if flex_quorum.read_quorum_size > num_nodes - flex_quorum.write_quorum_size + 1 {
warn!(
paxos.logger,
"Unnecessary overlaps in read and write quorums. Read and Write quorums only need to be overlapping by one node i.e., read_quorum_size + write_quorum_size = num_nodes + 1");
}
}
}
paxos
}
pub(crate) fn get_state(&self) -> &(Role, Phase) {
&self.state
}
pub(crate) fn get_promise(&self) -> Ballot {
self.internal_storage.get_promise()
}
pub(crate) fn trim(&mut self, trim_idx: Option<usize>) -> Result<(), CompactionErr> {
match self.state {
(Role::Leader, _) => {
let min_all_accepted_idx = self.leader_state.get_min_all_accepted_idx();
let trimmed_idx = match trim_idx {
Some(idx) if idx <= *min_all_accepted_idx => idx,
None => {
#[cfg(feature = "logging")]
trace!(
self.logger,
"No trim index provided, using min_las_idx: {:?}",
min_all_accepted_idx
);
*min_all_accepted_idx
}
_ => {
return Err(CompactionErr::NotAllDecided(*min_all_accepted_idx));
}
};
let result = self.internal_storage.try_trim(trimmed_idx);
if result.is_ok() {
for pid in &self.peers {
let msg = PaxosMsg::Compaction(Compaction::Trim(trimmed_idx));
self.outgoing.push(Message::SequencePaxos(PaxosMessage {
from: self.pid,
to: *pid,
msg,
}));
}
}
result.map_err(|e| {
*e.downcast()
.expect("storage error while trying to trim log")
})
}
_ => Err(CompactionErr::NotCurrentLeader(self.get_current_leader())),
}
}
pub(crate) fn snapshot(
&mut self,
idx: Option<usize>,
local_only: bool,
) -> Result<(), CompactionErr> {
let result = self.internal_storage.try_snapshot(idx);
if !local_only && result.is_ok() {
for pid in &self.peers {
let msg = PaxosMsg::Compaction(Compaction::Snapshot(idx));
self.outgoing.push(Message::SequencePaxos(PaxosMessage {
from: self.pid,
to: *pid,
msg,
}));
}
}
result.map_err(|e| {
*e.downcast()
.expect("storage error while trying to snapshot log")
})
}
pub(crate) fn get_decided_idx(&self) -> usize {
self.internal_storage.get_decided_idx()
}
pub(crate) fn get_compacted_idx(&self) -> usize {
self.internal_storage.get_compacted_idx()
}
fn handle_compaction(&mut self, c: Compaction) {
match c {
Compaction::Trim(idx) => {
let _ = self.internal_storage.try_trim(idx);
}
Compaction::Snapshot(idx) => {
let _ = self.snapshot(idx, true);
}
}
}
pub(crate) fn resend_message_timeout(&mut self) {
match self.state.0 {
Role::Leader => self.resend_messages_leader(),
Role::Follower => self.resend_messages_follower(),
}
}
pub(crate) fn flush_batch_timeout(&mut self) {
match self.state {
(Role::Leader, Phase::Accept) => self.flush_batch_leader(),
(Role::Follower, Phase::Accept) => self.flush_batch_follower(),
_ => (),
}
}
pub(crate) fn take_outgoing_msgs(&mut self, buffer: &mut Vec<Message<T>>) {
if buffer.is_empty() {
std::mem::swap(buffer, &mut self.outgoing);
} else {
buffer.append(&mut self.outgoing);
}
self.leader_state.reset_latest_accept_meta();
self.latest_accepted_meta = None;
}
pub(crate) fn handle(&mut self, m: PaxosMessage<T>) {
match m.msg {
PaxosMsg::PrepareReq(prepreq) => self.handle_preparereq(prepreq, m.from),
PaxosMsg::Prepare(prep) => self.handle_prepare(prep, m.from),
PaxosMsg::Promise(prom) => match &self.state {
(Role::Leader, Phase::Prepare) => self.handle_promise_prepare(prom, m.from),
(Role::Leader, Phase::Accept) => self.handle_promise_accept(prom, m.from),
_ => {}
},
PaxosMsg::AcceptSync(acc_sync) => self.handle_acceptsync(acc_sync, m.from),
PaxosMsg::AcceptDecide(acc) => self.handle_acceptdecide(acc),
PaxosMsg::NotAccepted(not_acc) => self.handle_notaccepted(not_acc, m.from),
PaxosMsg::Accepted(accepted) => self.handle_accepted(accepted, m.from),
PaxosMsg::Decide(d) => self.handle_decide(d),
PaxosMsg::ProposalForward(proposals) => self.handle_forwarded_proposal(proposals),
PaxosMsg::Compaction(c) => self.handle_compaction(c),
PaxosMsg::AcceptStopSign(acc_ss) => self.handle_accept_stopsign(acc_ss),
PaxosMsg::ForwardStopSign(f_ss) => self.handle_forwarded_stopsign(f_ss),
}
}
pub(crate) fn is_reconfigured(&self) -> Option<StopSign> {
match self.internal_storage.get_stopsign() {
Some(ss) if self.internal_storage.stopsign_is_decided() => Some(ss),
_ => None,
}
}
fn accepted_reconfiguration(&self) -> bool {
self.internal_storage.get_stopsign().is_some()
}
pub(crate) fn append(&mut self, entry: T) -> Result<(), ProposeErr<T>> {
if self.accepted_reconfiguration() {
Err(ProposeErr::PendingReconfigEntry(entry))
} else {
self.propose_entry(entry);
Ok(())
}
}
pub(crate) fn reconfigure(
&mut self,
new_config: ClusterConfig,
metadata: Option<Vec<u8>>,
) -> Result<(), ProposeErr<T>> {
if self.accepted_reconfiguration() {
return Err(ProposeErr::PendingReconfigConfig(new_config, metadata));
}
#[cfg(feature = "logging")]
info!(
self.logger,
"Accepting reconfiguration {:?}", new_config.nodes
);
let ss = StopSign::with(new_config, metadata);
match self.state {
(Role::Leader, Phase::Prepare) => self.buffered_stopsign = Some(ss),
(Role::Leader, Phase::Accept) => self.accept_stopsign_leader(ss),
_ => self.forward_stopsign(ss),
}
Ok(())
}
fn get_current_leader(&self) -> NodeId {
self.get_promise().pid
}
pub(crate) fn get_pid(&self) -> NodeId {
self.pid
}
pub(crate) fn reconnected(&mut self, pid: NodeId) {
if pid == self.pid {
return;
} else if pid == self.get_current_leader() {
self.state = (Role::Follower, Phase::Recover);
}
let prepreq = PrepareReq {
n: self.get_promise(),
};
self.outgoing.push(Message::SequencePaxos(PaxosMessage {
from: self.pid,
to: pid,
msg: PaxosMsg::PrepareReq(prepreq),
}));
}
fn propose_entry(&mut self, entry: T) {
match self.state {
(Role::Leader, Phase::Prepare) => self.buffered_proposals.push(entry),
(Role::Leader, Phase::Accept) => self.accept_entry_leader(entry),
_ => self.forward_proposals(vec![entry]),
}
}
pub(crate) fn get_leader_state(&self) -> &LeaderState<T> {
&self.leader_state
}
pub(crate) fn forward_proposals(&mut self, mut entries: Vec<T>) {
let leader = self.get_current_leader();
if leader > 0 && self.pid != leader {
let pf = PaxosMsg::ProposalForward(entries);
let msg = Message::SequencePaxos(PaxosMessage {
from: self.pid,
to: leader,
msg: pf,
});
self.outgoing.push(msg);
} else {
self.buffered_proposals.append(&mut entries);
}
}
pub(crate) fn forward_stopsign(&mut self, ss: StopSign) {
let leader = self.get_current_leader();
if leader > 0 && self.pid != leader {
#[cfg(feature = "logging")]
trace!(self.logger, "Forwarding StopSign to Leader {:?}", leader);
let fs = PaxosMsg::ForwardStopSign(ss);
let msg = Message::SequencePaxos(PaxosMessage {
from: self.pid,
to: leader,
msg: fs,
});
self.outgoing.push(msg);
} else if self.buffered_stopsign.as_mut().is_none() {
self.buffered_stopsign = Some(ss);
}
}
fn create_log_sync(
&self,
common_prefix_idx: usize,
other_logs_decided_idx: usize,
) -> LogSync<T> {
let decided_idx = self.internal_storage.get_decided_idx();
let (decided_snapshot, suffix, sync_idx) =
if T::Snapshot::use_snapshots() && decided_idx > common_prefix_idx {
let (delta_snapshot, compacted_idx) = self
.internal_storage
.create_diff_snapshot(other_logs_decided_idx)
.expect(READ_ERROR_MSG);
let suffix = self
.internal_storage
.get_suffix(decided_idx)
.expect(READ_ERROR_MSG);
(delta_snapshot, suffix, compacted_idx)
} else {
let suffix = self
.internal_storage
.get_suffix(common_prefix_idx)
.expect(READ_ERROR_MSG);
(None, suffix, common_prefix_idx)
};
LogSync {
decided_snapshot,
suffix,
sync_idx,
stopsign: self.internal_storage.get_stopsign(),
}
}
}
#[derive(PartialEq, Debug)]
pub(crate) enum Phase {
Prepare,
Accept,
Recover,
None,
}
#[derive(PartialEq, Debug)]
pub(crate) enum Role {
Follower,
Leader,
}
#[derive(Clone, Debug)]
pub(crate) struct SequencePaxosConfig {
pid: NodeId,
peers: Vec<NodeId>,
buffer_size: usize,
pub(crate) batch_size: usize,
flexible_quorum: Option<FlexibleQuorum>,
#[cfg(feature = "logging")]
logger_file_path: Option<String>,
#[cfg(feature = "logging")]
custom_logger: Option<Logger>,
}
impl From<OmniPaxosConfig> for SequencePaxosConfig {
fn from(config: OmniPaxosConfig) -> Self {
let pid = config.server_config.pid;
let peers = config
.cluster_config
.nodes
.into_iter()
.filter(|x| *x != pid)
.collect();
SequencePaxosConfig {
pid,
peers,
flexible_quorum: config.cluster_config.flexible_quorum,
buffer_size: config.server_config.buffer_size,
batch_size: config.server_config.batch_size,
#[cfg(feature = "logging")]
logger_file_path: config.server_config.logger_file_path,
#[cfg(feature = "logging")]
custom_logger: config.server_config.custom_logger,
}
}
}