use super::{DurabilityError, DurableStore};
pub async fn append_settled(
store: &dyn DurableStore,
stream_key: &str,
payload: Vec<u8>,
expected_seq: u64,
) -> Result<u64, DurabilityError> {
match store
.append(stream_key, payload.clone(), expected_seq)
.await
{
Err(unknown @ DurabilityError::OutcomeUnknown { .. }) => {
match own_row(store, stream_key, expected_seq, &payload).await {
Err(read) => Err(unread(unknown, stream_key, &read)),
Ok(Standing::Own) => Ok(expected_seq),
Ok(Standing::Other) => Err(conflict(expected_seq)),
Ok(Standing::Empty) => {
match store
.append(stream_key, payload.clone(), expected_seq)
.await
{
Err(resent @ DurabilityError::SequenceConflict { .. }) => {
match own_row(store, stream_key, expected_seq, &payload).await {
Ok(Standing::Own) => Ok(expected_seq),
Ok(_) => Err(resent),
Err(read) => Err(unread(unknown, stream_key, &read)),
}
}
outcome => outcome,
}
}
}
}
outcome => outcome,
}
}
pub async fn append_all_settled(
store: &dyn DurableStore,
stream_key: &str,
payloads: Vec<Vec<u8>>,
expected_seq: u64,
) -> Result<u64, DurabilityError> {
match store
.append_all(stream_key, payloads.clone(), expected_seq)
.await
{
Err(unknown @ DurabilityError::OutcomeUnknown { .. }) => {
let after = after_batch(stream_key, expected_seq, payloads.len())?;
match own_rows(store, stream_key, expected_seq, &payloads).await {
Err(read) => Err(unread(unknown, stream_key, &read)),
Ok(Standing::Own) => Ok(after),
Ok(Standing::Other) => Err(conflict(expected_seq)),
Ok(Standing::Empty) => {
match store
.append_all(stream_key, payloads.clone(), expected_seq)
.await
{
Err(resent @ DurabilityError::SequenceConflict { .. }) => {
match own_rows(store, stream_key, expected_seq, &payloads).await {
Ok(Standing::Own) => Ok(after),
Ok(_) => Err(resent),
Err(read) => Err(unread(unknown, stream_key, &read)),
}
}
outcome => outcome,
}
}
}
}
outcome => outcome,
}
}
pub async fn cas_settled(
store: &dyn DurableStore,
key: &str,
old_value: u64,
new_value: u64,
) -> Result<(), DurabilityError> {
match store.cas(key, old_value, new_value).await {
Err(unknown @ DurabilityError::OutcomeUnknown { .. }) => {
let stored = match store.read_value(key).await {
Ok(stored) => stored.unwrap_or(0),
Err(read) => return Err(unread(unknown, key, &read)),
};
if stored == new_value {
return Ok(());
}
if stored != old_value {
return Err(DurabilityError::CursorRegression {
stored,
attempted: old_value,
});
}
match store.cas(key, old_value, new_value).await {
Err(resent @ DurabilityError::CursorRegression { .. }) => {
match store.read_value(key).await {
Ok(stored) if stored.unwrap_or(0) == new_value => Ok(()),
Ok(_) => Err(resent),
Err(read) => Err(unread(unknown, key, &read)),
}
}
outcome => outcome,
}
}
outcome => outcome,
}
}
pub async fn flush_settled(store: &dyn DurableStore) -> Result<(), DurabilityError> {
match store.flush().await {
Err(DurabilityError::OutcomeUnknown { .. }) => store.flush().await,
outcome => outcome,
}
}
const fn conflict(expected_seq: u64) -> DurabilityError {
DurabilityError::SequenceConflict {
expected: expected_seq,
actual: expected_seq.saturating_add(1),
}
}
fn unread(unknown: DurabilityError, key: &str, read: &DurabilityError) -> DurabilityError {
tracing::warn!(
key,
%read,
"a write's unknown outcome could not be read back; it stays unknown"
);
unknown
}
enum Standing {
Own,
Empty,
Other,
}
async fn own_row(
store: &dyn DurableStore,
stream_key: &str,
sequence: u64,
payload: &[u8],
) -> Result<Standing, DurabilityError> {
Ok(match store.read_at(stream_key, sequence).await? {
None => Standing::Empty,
Some(entry) if entry.payload == payload => Standing::Own,
Some(_) => Standing::Other,
})
}
async fn own_rows(
store: &dyn DurableStore,
stream_key: &str,
expected_seq: u64,
payloads: &[Vec<u8>],
) -> Result<Standing, DurabilityError> {
let standing = store
.read_from(stream_key, expected_seq, payloads.len())
.await?;
if standing.is_empty() {
return Ok(Standing::Empty);
}
let own =
standing.len() == payloads.len()
&& standing.iter().zip(payloads).zip(expected_seq..).all(
|((entry, payload), sequence)| {
entry.sequence == sequence && entry.payload == *payload
},
);
Ok(if own { Standing::Own } else { Standing::Other })
}
fn after_batch(stream_key: &str, expected_seq: u64, rows: usize) -> Result<u64, DurabilityError> {
u64::try_from(rows)
.ok()
.and_then(|rows| expected_seq.checked_add(rows))
.ok_or_else(|| {
DurabilityError::ConfigError(format!(
"{stream_key}: sequence overflow settling a batch at {expected_seq}"
))
})
}