use std::fmt;
use super::DurableChannel;
use crate::durability::DurabilityError;
#[derive(Clone)]
pub struct UnresolvedAppend {
channel: String,
partition: usize,
sequence: u64,
payload: Vec<u8>,
idempotency_key: Option<String>,
cause: String,
}
impl UnresolvedAppend {
pub(crate) fn new(
channel: String,
partition: usize,
sequence: u64,
payload: Vec<u8>,
idempotency_key: Option<String>,
) -> Self {
Self {
channel,
partition,
sequence,
payload,
idempotency_key,
cause: "append future did not return an outcome".to_owned(),
}
}
#[must_use]
pub fn channel(&self) -> &str {
&self.channel
}
#[must_use]
pub const fn partition(&self) -> usize {
self.partition
}
#[must_use]
pub const fn sequence(&self) -> u64 {
self.sequence
}
#[must_use]
pub fn cause(&self) -> &str {
&self.cause
}
#[must_use]
pub fn idempotency_key(&self) -> Option<&str> {
self.idempotency_key.as_deref()
}
pub(crate) fn set_cause(&mut self, cause: String) {
self.cause = cause;
}
pub(crate) fn payload(&self) -> &[u8] {
&self.payload
}
}
impl fmt::Display for UnresolvedAppend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"channel {} partition {} sequence {}: {}",
self.channel, self.partition, self.sequence, self.cause
)
}
}
impl fmt::Debug for UnresolvedAppend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnresolvedAppend")
.field("channel", &self.channel)
.field("partition", &self.partition)
.field("sequence", &self.sequence)
.field("byte_length", &self.payload.len())
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AppendResolution {
Committed { position: u64 },
Absent,
}
impl DurableChannel {
#[must_use]
pub fn pending_append(&self) -> Option<UnresolvedAppend> {
self.pending.clone()
}
pub async fn resolve_pending(&mut self) -> Result<Option<AppendResolution>, DurabilityError> {
let Some(pending) = self.pending.clone() else {
return Ok(None);
};
let stream_key = self.stream_key_for(pending.partition);
let row = self
.store
.read_at(&stream_key, pending.sequence)
.await
.map_err(|error| DurabilityError::PendingAppendUnreadable {
stream_key: stream_key.clone(),
sequence: pending.sequence,
append_error: pending.cause.clone(),
read_error: error.to_string(),
})?;
let Some(row) = row else {
self.pending = None;
return Ok(Some(AppendResolution::Absent));
};
let next = row.sequence.checked_add(1).ok_or_else(|| {
DurabilityError::ConfigError(format!(
"channel {stream_key} sequence overflow during recovery"
))
})?;
if row.sequence != pending.sequence || row.payload != pending.payload {
self.pending = None;
self.refresh_sequence(pending.partition, &stream_key, pending.sequence, next)
.await?;
return Ok(Some(AppendResolution::Absent));
}
self.pending = None;
self.advance_partition(pending.partition, &stream_key, next)
.await?;
Ok(Some(AppendResolution::Committed {
position: row.sequence,
}))
}
}