use std::fmt;
use std::sync::{Mutex, OnceLock, PoisonError};
use liminal::durability::{
DurabilityError, DurableStore, EphemeralHaematiteStore, ScanCursor, ScanPage, StoredEntry,
};
pub(super) type ThrowawayStoreOpener =
Box<dyn Fn() -> Result<EphemeralHaematiteStore, DurabilityError> + Send + Sync>;
pub(super) struct DeferredStore {
open: ThrowawayStoreOpener,
store: OnceLock<EphemeralHaematiteStore>,
opening: Mutex<()>,
}
impl DeferredStore {
pub(super) fn new(open: ThrowawayStoreOpener) -> Self {
Self {
open,
store: OnceLock::new(),
opening: Mutex::new(()),
}
}
fn opened(&self) -> Option<&EphemeralHaematiteStore> {
self.store.get()
}
fn for_write(&self) -> Result<&EphemeralHaematiteStore, DurabilityError> {
if let Some(store) = self.store.get() {
return Ok(store);
}
let _opening = self.opening.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(store) = self.store.get() {
return Ok(store);
}
let store = (self.open)()?;
Ok(self.store.get_or_init(|| store))
}
}
impl fmt::Debug for DeferredStore {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DeferredStore")
.field("store", &self.store.get())
.finish_non_exhaustive()
}
}
#[async_trait::async_trait]
impl DurableStore for DeferredStore {
async fn apply_routed_batch(
&self,
route_key: &str,
items: Vec<liminal::durability::RoutedBatchItem>,
) -> Result<(), DurabilityError> {
if items.is_empty() {
return Ok(());
}
self.for_write()?.apply_routed_batch(route_key, items).await
}
async fn get_routed(
&self,
route_key: &str,
key: &[u8],
) -> Result<Option<Vec<u8>>, DurabilityError> {
match self.opened() {
Some(store) => store.get_routed(route_key, key).await,
None => Ok(None),
}
}
async fn append(
&self,
stream_key: &str,
payload: Vec<u8>,
expected_seq: u64,
) -> Result<u64, DurabilityError> {
self.for_write()?
.append(stream_key, payload, expected_seq)
.await
}
async fn append_all(
&self,
stream_key: &str,
payloads: Vec<Vec<u8>>,
expected_seq: u64,
) -> Result<u64, DurabilityError> {
self.for_write()?
.append_all(stream_key, payloads, expected_seq)
.await
}
async fn read_from(
&self,
stream_key: &str,
offset: u64,
limit: usize,
) -> Result<Vec<StoredEntry>, DurabilityError> {
match self.opened() {
Some(store) => store.read_from(stream_key, offset, limit).await,
None => Ok(Vec::new()),
}
}
async fn read_at(
&self,
stream_key: &str,
sequence: u64,
) -> Result<Option<StoredEntry>, DurabilityError> {
match self.opened() {
Some(store) => store.read_at(stream_key, sequence).await,
None => Ok(None),
}
}
async fn cas(&self, key: &str, old_value: u64, new_value: u64) -> Result<(), DurabilityError> {
self.for_write()?.cas(key, old_value, new_value).await
}
async fn read_stream_next_seq(&self, stream_key: &str) -> Result<Option<u64>, DurabilityError> {
match self.opened() {
Some(store) => store.read_stream_next_seq(stream_key).await,
None => Ok(None),
}
}
async fn read_value(&self, key: &str) -> Result<Option<u64>, DurabilityError> {
match self.opened() {
Some(store) => store.read_value(key).await,
None => Ok(None),
}
}
async fn scan_routed(
&self,
route_key: &str,
from: &[u8],
to: &[u8],
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, DurabilityError> {
match self.opened() {
Some(store) => store.scan_routed(route_key, from, to).await,
None => Ok(Vec::new()),
}
}
async fn remove_routed_range(
&self,
route_key: &str,
from: &[u8],
to: &[u8],
) -> Result<u64, DurabilityError> {
match self.opened() {
Some(store) => store.remove_routed_range(route_key, from, to).await,
None => Ok(0),
}
}
async fn scan_page(
&self,
prefix: &str,
cursor: Option<ScanCursor>,
limit: usize,
) -> Result<ScanPage, DurabilityError> {
match self.opened() {
Some(store) => store.scan_page(prefix, cursor, limit).await,
None => Ok(ScanPage {
entries: Vec::new(),
next: None,
}),
}
}
async fn flush(&self) -> Result<(), DurabilityError> {
match self.opened() {
Some(store) => store.flush().await,
None => Ok(()),
}
}
async fn truncate_before(
&self,
stream_key: &str,
before_seq: u64,
) -> Result<u64, DurabilityError> {
match self.opened() {
Some(store) => store.truncate_before(stream_key, before_seq).await,
None => Ok(0),
}
}
}
#[cfg(test)]
#[path = "deferred_store_batch_tests.rs"]
mod batch_tests;