use std::fmt::Debug;
use std::ops::RangeBounds;
use std::sync::Arc;
use openraft::LogId;
use openraft::OptionalSend;
use openraft::RaftLogReader;
use openraft::RaftTypeConfig;
use openraft::alias::LogIdOf;
use openraft::log_id::LogIndexOptionExt;
use openraft::log_id::RaftLogId;
use openraft::storage::IOFlushed;
use openraft::storage::LogState;
use openraft::storage::RaftLogStorage;
use tokio::sync::Mutex;
use crate::app::EzApp;
use crate::meta::EzMeta;
use crate::storage::EzStorage;
use crate::storage::Persist;
use crate::storage::adapter::SharedStorage;
use crate::type_config::OpenRaftTypes;
pub struct LogStore<T>
where T: EzApp
{
storage: SharedStorage<T>,
meta: Arc<Mutex<EzMeta>>,
}
impl<T> Clone for LogStore<T>
where T: EzApp
{
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
meta: self.meta.clone(),
}
}
}
impl<T> LogStore<T>
where T: EzApp
{
pub(super) fn new(storage: SharedStorage<T>, meta: EzMeta) -> Self {
Self {
storage,
meta: Arc::new(Mutex::new(meta)),
}
}
pub async fn save_meta(&self, f: impl FnOnce(&mut EzMeta)) -> Result<(), std::io::Error> {
let mut meta = self.meta.lock().await;
f(&mut meta);
let mut storage = self.storage.lock().await;
persist_meta(&mut meta, &mut **storage).await
}
pub async fn node_id(&self) -> Option<u64> {
self.meta.lock().await.node_id
}
}
async fn persist_meta<T>(meta: &mut EzMeta, storage: &mut dyn EzStorage<T>) -> Result<(), std::io::Error>
where T: EzApp {
meta.last_log_id = meta.last_log_id.max(meta.last_purged);
storage.persist(Persist::Meta(meta.clone())).await
}
impl<T> RaftLogStorage<OpenRaftTypes<T>> for LogStore<T>
where T: EzApp
{
type LogReader = Self;
async fn get_log_state(&mut self) -> Result<LogState<OpenRaftTypes<T>>, std::io::Error> {
let meta = self.meta.lock().await;
let last = meta.last_log_id.map(|(t, i)| LogId::new_term_index(t, i));
let last_purged = meta.last_purged.map(|(t, i)| LogId::new_term_index(t, i));
Ok(LogState {
last_log_id: last,
last_purged_log_id: last_purged,
})
}
async fn save_vote(&mut self, vote: &<OpenRaftTypes<T> as RaftTypeConfig>::Vote) -> Result<(), std::io::Error> {
self.save_meta(|m| m.vote = Some(*vote)).await
}
async fn append<I>(&mut self, entries: I, callback: IOFlushed<OpenRaftTypes<T>>) -> Result<(), std::io::Error>
where
I: IntoIterator<Item = <OpenRaftTypes<T> as RaftTypeConfig>::Entry> + OptionalSend,
I::IntoIter: OptionalSend,
{
let res = async {
let mut meta = self.meta.lock().await;
let mut storage = self.storage.lock().await;
let mut last_log_id = None;
for entry in entries {
last_log_id = Some(entry.log_id);
storage.persist(Persist::LogEntry(entry)).await?;
}
if let Some(log_id) = last_log_id {
meta.last_log_id = Some(log_id);
persist_meta(&mut meta, &mut **storage).await?;
}
Ok::<_, std::io::Error>(())
}
.await;
match res {
Ok(()) => {
callback.io_completed(Ok(()));
Ok(())
}
Err(e) => {
callback.io_completed(Err(std::io::Error::other(e.to_string())));
Err(e)
}
}
}
async fn truncate_after(&mut self, last_log_id: Option<LogIdOf<OpenRaftTypes<T>>>) -> Result<(), std::io::Error> {
let from = last_log_id.map(|id| id.index).next_index();
let mut meta = self.meta.lock().await;
let mut storage = self.storage.lock().await;
meta.last_log_id = last_log_id.map(|id| id.to_type());
persist_meta(&mut meta, &mut **storage).await?;
storage.persist(Persist::DeleteLogs { from, to: u64::MAX }).await
}
async fn purge(&mut self, log_id: LogIdOf<OpenRaftTypes<T>>) -> Result<(), std::io::Error> {
let mut meta = self.meta.lock().await;
let mut storage = self.storage.lock().await;
meta.last_purged = Some(log_id.to_type());
persist_meta(&mut meta, &mut **storage).await?;
storage
.persist(Persist::DeleteLogs {
from: 0,
to: log_id.index + 1,
})
.await
}
async fn get_log_reader(&mut self) -> Self::LogReader {
self.clone()
}
}
impl<T> RaftLogReader<OpenRaftTypes<T>> for LogStore<T>
where T: EzApp
{
async fn read_vote(&mut self) -> Result<Option<<OpenRaftTypes<T> as RaftTypeConfig>::Vote>, std::io::Error> {
Ok(self.meta.lock().await.vote)
}
async fn try_get_log_entries<RB>(
&mut self,
range: RB,
) -> Result<Vec<<OpenRaftTypes<T> as RaftTypeConfig>::Entry>, std::io::Error>
where
RB: RangeBounds<u64> + Clone + Debug + OptionalSend,
{
let meta = self.meta.lock().await;
let mut storage = self.storage.lock().await;
let lo = meta.last_purged.map(|(_, i)| i).next_index();
let hi = meta.last_log_id.map(|(_, i)| i).next_index();
let start = match range.start_bound() {
std::ops::Bound::Included(&x) => x,
std::ops::Bound::Excluded(&x) => x + 1,
std::ops::Bound::Unbounded => 0,
};
let end = match range.end_bound() {
std::ops::Bound::Included(&x) => x + 1,
std::ops::Bound::Excluded(&x) => x,
std::ops::Bound::Unbounded => hi,
};
let start = std::cmp::max(start, lo);
let end = std::cmp::min(end, hi);
if start >= end {
return Ok(Vec::new());
}
storage.read_logs(start, end).await
}
}
#[cfg(test)]
mod tests {
use std::sync::Mutex as StdMutex;
use async_trait::async_trait;
use openraft::LogId;
use super::*;
use crate::entry::EzEntry;
use crate::storage::Loaded;
use crate::storage::adapter::open;
#[derive(serde::Deserialize, serde::Serialize)]
struct Marker;
#[async_trait]
impl EzApp for Marker {
type Request = String;
type Response = String;
async fn apply(&mut self, req: String) -> String {
req
}
type ReadRequest = ();
type ReadResponse = ();
fn read(&self, _req: ()) {}
}
#[derive(Clone, Default)]
struct Recorder {
ops: Arc<StdMutex<Vec<String>>>,
}
#[async_trait]
impl EzStorage<Marker> for Recorder {
async fn load(&mut self) -> Result<Loaded, std::io::Error> {
Ok(Loaded {
meta: EzMeta::default(),
snapshot: None,
})
}
async fn persist(&mut self, op: Persist<Marker>) -> Result<(), std::io::Error> {
self.ops.lock().unwrap().push(op.to_string());
Ok(())
}
async fn read_logs(&mut self, _start: u64, _end: u64) -> Result<Vec<EzEntry<Marker>>, std::io::Error> {
unreachable!("these tests only write")
}
}
async fn recording_log_store() -> (LogStore<Marker>, Arc<StdMutex<Vec<String>>>) {
let recorder = Recorder::default();
let ops = recorder.ops.clone();
let (log, _sm) = open(recorder, Marker).await.unwrap();
(log, ops)
}
#[tokio::test]
async fn purge_records_the_purge_point_before_deleting() {
let (mut log, ops) = recording_log_store().await;
log.purge(LogId::new_term_index(1, 9)).await.unwrap();
assert_eq!(["Meta", "DeleteLogs(0..10)"], ops.lock().unwrap()[..]);
}
#[tokio::test]
async fn truncate_records_the_new_end_before_deleting() {
let (mut log, ops) = recording_log_store().await;
log.truncate_after(Some(LogId::new_term_index(1, 4))).await.unwrap();
assert_eq!(
["Meta".to_string(), format!("DeleteLogs(5..{})", u64::MAX)],
ops.lock().unwrap()[..]
);
}
}