#![cfg_attr(feature = "docinclude", feature(external_doc))]
#![cfg_attr(feature = "docinclude", doc(include = "../README.md"))]
#[cfg(test)]
mod test;
use std::collections::{BTreeMap, HashMap};
use std::io::Cursor;
use anyhow::Result;
use async_raft::async_trait::async_trait;
use async_raft::raft::{Entry, EntryPayload, MembershipConfig};
use async_raft::storage::{CurrentSnapshotData, HardState, InitialState};
use async_raft::{AppData, AppDataResponse, NodeId, RaftStorage};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::sync::RwLock;
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
const ERR_INCONSISTENT_LOG: &str = "a query was received which was expecting data to be in place which does not exist in the log";
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientRequest {
pub client: String,
pub serial: u64,
pub status: String,
}
impl AppData for ClientRequest {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientResponse(Option<String>);
impl AppDataResponse for ClientResponse {}
#[derive(Clone, Debug, Error)]
pub enum ShutdownError {
#[error("unsafe storage error")]
UnsafeStorageError,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MemStoreSnapshot {
pub index: u64,
pub term: u64,
pub membership: MembershipConfig,
pub data: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct MemStoreStateMachine {
pub last_applied_log: u64,
pub client_serial_responses: HashMap<String, (u64, Option<String>)>,
pub client_status: HashMap<String, String>,
}
pub struct MemStore {
id: NodeId,
log: RwLock<BTreeMap<u64, Entry<ClientRequest>>>,
sm: RwLock<MemStoreStateMachine>,
hs: RwLock<Option<HardState>>,
current_snapshot: RwLock<Option<MemStoreSnapshot>>,
}
impl MemStore {
pub fn new(id: NodeId) -> Self {
let log = RwLock::new(BTreeMap::new());
let sm = RwLock::new(MemStoreStateMachine::default());
let hs = RwLock::new(None);
let current_snapshot = RwLock::new(None);
Self {
id,
log,
sm,
hs,
current_snapshot,
}
}
#[cfg(test)]
pub fn new_with_state(
id: NodeId, log: BTreeMap<u64, Entry<ClientRequest>>, sm: MemStoreStateMachine, hs: Option<HardState>,
current_snapshot: Option<MemStoreSnapshot>,
) -> Self {
let log = RwLock::new(log);
let sm = RwLock::new(sm);
let hs = RwLock::new(hs);
let current_snapshot = RwLock::new(current_snapshot);
Self {
id,
log,
sm,
hs,
current_snapshot,
}
}
pub async fn get_log(&self) -> RwLockWriteGuard<'_, BTreeMap<u64, Entry<ClientRequest>>> {
self.log.write().await
}
pub async fn get_state_machine(&self) -> RwLockWriteGuard<'_, MemStoreStateMachine> {
self.sm.write().await
}
pub async fn read_hard_state(&self) -> RwLockReadGuard<'_, Option<HardState>> {
self.hs.read().await
}
}
#[async_trait]
impl RaftStorage<ClientRequest, ClientResponse> for MemStore {
type Snapshot = Cursor<Vec<u8>>;
type ShutdownError = ShutdownError;
#[tracing::instrument(level = "trace", skip(self))]
async fn get_membership_config(&self) -> Result<MembershipConfig> {
let log = self.log.read().await;
let cfg_opt = log.values().rev().find_map(|entry| match &entry.payload {
EntryPayload::ConfigChange(cfg) => Some(cfg.membership.clone()),
EntryPayload::SnapshotPointer(snap) => Some(snap.membership.clone()),
_ => None,
});
Ok(match cfg_opt {
Some(cfg) => cfg,
None => MembershipConfig::new_initial(self.id),
})
}
#[tracing::instrument(level = "trace", skip(self))]
async fn get_initial_state(&self) -> Result<InitialState> {
let membership = self.get_membership_config().await?;
let mut hs = self.hs.write().await;
let log = self.log.read().await;
let sm = self.sm.read().await;
match &mut *hs {
Some(inner) => {
let (last_log_index, last_log_term) = match log.values().rev().next() {
Some(log) => (log.index, log.term),
None => (0, 0),
};
let last_applied_log = sm.last_applied_log;
Ok(InitialState {
last_log_index,
last_log_term,
last_applied_log,
hard_state: inner.clone(),
membership,
})
}
None => {
let new = InitialState::new_initial(self.id);
*hs = Some(new.hard_state.clone());
Ok(new)
}
}
}
#[tracing::instrument(level = "trace", skip(self, hs))]
async fn save_hard_state(&self, hs: &HardState) -> Result<()> {
*self.hs.write().await = Some(hs.clone());
Ok(())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn get_log_entries(&self, start: u64, stop: u64) -> Result<Vec<Entry<ClientRequest>>> {
if start > stop {
tracing::error!("invalid request, start > stop");
return Ok(vec![]);
}
let log = self.log.read().await;
Ok(log.range(start..stop).map(|(_, val)| val.clone()).collect())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn delete_logs_from(&self, start: u64, stop: Option<u64>) -> Result<()> {
if stop.as_ref().map(|stop| &start > stop).unwrap_or(false) {
tracing::error!("invalid request, start > stop");
return Ok(());
}
let mut log = self.log.write().await;
if let Some(stop) = stop.as_ref() {
for key in start..*stop {
log.remove(&key);
}
return Ok(());
}
log.split_off(&start);
Ok(())
}
#[tracing::instrument(level = "trace", skip(self, entry))]
async fn append_entry_to_log(&self, entry: &Entry<ClientRequest>) -> Result<()> {
let mut log = self.log.write().await;
log.insert(entry.index, entry.clone());
Ok(())
}
#[tracing::instrument(level = "trace", skip(self, entries))]
async fn replicate_to_log(&self, entries: &[Entry<ClientRequest>]) -> Result<()> {
let mut log = self.log.write().await;
for entry in entries {
log.insert(entry.index, entry.clone());
}
Ok(())
}
#[tracing::instrument(level = "trace", skip(self, data))]
async fn apply_entry_to_state_machine(&self, index: &u64, data: &ClientRequest) -> Result<ClientResponse> {
let mut sm = self.sm.write().await;
sm.last_applied_log = *index;
if let Some((serial, res)) = sm.client_serial_responses.get(&data.client) {
if serial == &data.serial {
return Ok(ClientResponse(res.clone()));
}
}
let previous = sm.client_status.insert(data.client.clone(), data.status.clone());
sm.client_serial_responses.insert(data.client.clone(), (data.serial, previous.clone()));
Ok(ClientResponse(previous))
}
#[tracing::instrument(level = "trace", skip(self, entries))]
async fn replicate_to_state_machine(&self, entries: &[(&u64, &ClientRequest)]) -> Result<()> {
let mut sm = self.sm.write().await;
for (index, data) in entries {
sm.last_applied_log = **index;
if let Some((serial, _)) = sm.client_serial_responses.get(&data.client) {
if serial == &data.serial {
continue;
}
}
let previous = sm.client_status.insert(data.client.clone(), data.status.clone());
sm.client_serial_responses.insert(data.client.clone(), (data.serial, previous.clone()));
}
Ok(())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn do_log_compaction(&self) -> Result<CurrentSnapshotData<Self::Snapshot>> {
let (data, last_applied_log);
{
let sm = self.sm.read().await;
data = serde_json::to_vec(&*sm)?;
last_applied_log = sm.last_applied_log;
}
let membership_config;
{
let log = self.log.read().await;
membership_config = log
.values()
.rev()
.skip_while(|entry| entry.index > last_applied_log)
.find_map(|entry| match &entry.payload {
EntryPayload::ConfigChange(cfg) => Some(cfg.membership.clone()),
_ => None,
})
.unwrap_or_else(|| MembershipConfig::new_initial(self.id));
}
let snapshot_bytes: Vec<u8>;
let term;
{
let mut log = self.log.write().await;
let mut current_snapshot = self.current_snapshot.write().await;
term = log
.get(&last_applied_log)
.map(|entry| entry.term)
.ok_or_else(|| anyhow::anyhow!(ERR_INCONSISTENT_LOG))?;
*log = log.split_off(&last_applied_log);
log.insert(
last_applied_log,
Entry::new_snapshot_pointer(last_applied_log, term, "".into(), membership_config.clone()),
);
let snapshot = MemStoreSnapshot {
index: last_applied_log,
term,
membership: membership_config.clone(),
data,
};
snapshot_bytes = serde_json::to_vec(&snapshot)?;
*current_snapshot = Some(snapshot);
}
tracing::trace!({ snapshot_size = snapshot_bytes.len() }, "log compaction complete");
Ok(CurrentSnapshotData {
term,
index: last_applied_log,
membership: membership_config.clone(),
snapshot: Box::new(Cursor::new(snapshot_bytes)),
})
}
#[tracing::instrument(level = "trace", skip(self))]
async fn create_snapshot(&self) -> Result<(String, Box<Self::Snapshot>)> {
Ok((String::from(""), Box::new(Cursor::new(Vec::new())))) }
#[tracing::instrument(level = "trace", skip(self, snapshot))]
async fn finalize_snapshot_installation(
&self, index: u64, term: u64, delete_through: Option<u64>, id: String, snapshot: Box<Self::Snapshot>,
) -> Result<()> {
tracing::trace!({ snapshot_size = snapshot.get_ref().len() }, "decoding snapshot for installation");
let raw = serde_json::to_string_pretty(snapshot.get_ref().as_slice())?;
println!("JSON SNAP:\n{}", raw);
let new_snapshot: MemStoreSnapshot = serde_json::from_slice(snapshot.get_ref().as_slice())?;
{
let mut log = self.log.write().await;
let membership_config = log
.values()
.rev()
.skip_while(|entry| entry.index > index)
.find_map(|entry| match &entry.payload {
EntryPayload::ConfigChange(cfg) => Some(cfg.membership.clone()),
_ => None,
})
.unwrap_or_else(|| MembershipConfig::new_initial(self.id));
match &delete_through {
Some(through) => {
*log = log.split_off(&(through + 1));
}
None => log.clear(),
}
log.insert(index, Entry::new_snapshot_pointer(index, term, id, membership_config));
}
{
let new_sm: MemStoreStateMachine = serde_json::from_slice(&new_snapshot.data)?;
let mut sm = self.sm.write().await;
*sm = new_sm;
}
let mut current_snapshot = self.current_snapshot.write().await;
*current_snapshot = Some(new_snapshot);
Ok(())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn get_current_snapshot(&self) -> Result<Option<CurrentSnapshotData<Self::Snapshot>>> {
match &*self.current_snapshot.read().await {
Some(snapshot) => {
let reader = serde_json::to_vec(&snapshot)?;
Ok(Some(CurrentSnapshotData {
index: snapshot.index,
term: snapshot.term,
membership: snapshot.membership.clone(),
snapshot: Box::new(Cursor::new(reader)),
}))
}
None => Ok(None),
}
}
}