use super::*;
use crate::{StateMachine, StepInputOf};
#[cfg(kani)]
mod proofs;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MutationStatus {
Ready,
AwaitingReply,
Uncertain,
Confirmed,
}
pub struct PendingMutation<Command> {
receipt: Receipt,
command: Command,
guarantee: Guarantee,
request: Option<RequestId>,
status: MutationStatus,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MutationReceiveError {
Unexpected,
WrongReceipt,
InsufficientGuarantee,
}
pub struct ReceiveMutation<'a, Value, Outcome, Refusal> {
pub id: RequestId,
pub reply: &'a Result<TableReply<Value, Outcome, Refusal>, StorageFailure>,
}
impl<Command> StateMachine for PendingMutation<Command> {}
impl<Command> PendingMutation<Command> {
pub fn new<Query>(
request: TableRequest<Query, Command>,
) -> Result<Self, TableRequest<Query, Command>> {
match request {
TableRequest::Mutate {
receipt,
command,
guarantee,
} => Ok(Self {
receipt,
command,
guarantee,
request: None,
status: MutationStatus::Ready,
}),
other @ (TableRequest::Read { .. }
| TableRequest::Inquire { .. }
| TableRequest::Retire { .. }) => Err(other),
}
}
pub const fn receipt(&self) -> Receipt {
self.receipt
}
pub const fn status(&self) -> MutationStatus {
self.status
}
pub const fn command(&self) -> &Command {
&self.command
}
pub const fn guarantee(&self) -> Guarantee {
self.guarantee
}
pub fn retry<Query, View>(
&mut self,
id: RequestId,
) -> Result<ContinuousRequest<Query, Command, View>, MutationStatus>
where
Command: Clone,
{
match self.status {
MutationStatus::Ready | MutationStatus::Uncertain => {}
MutationStatus::AwaitingReply | MutationStatus::Confirmed => return Err(self.status),
}
let command = self.command.clone();
self.request = Some(id);
self.status = MutationStatus::AwaitingReply;
Ok(ContinuousRequest::Command {
id,
request: TableRequest::Mutate {
receipt: self.receipt,
command,
guarantee: self.guarantee,
},
})
}
pub fn inquire<Query, View>(
&mut self,
id: RequestId,
) -> Result<ContinuousRequest<Query, Command, View>, MutationStatus> {
if self.status != MutationStatus::Uncertain {
return Err(self.status);
}
self.request = Some(id);
self.status = MutationStatus::AwaitingReply;
Ok(ContinuousRequest::Command {
id,
request: TableRequest::Inquire {
receipt: self.receipt,
guarantee: self.guarantee,
},
})
}
pub fn disconnect(&mut self) -> MutationStatus {
if self.status == MutationStatus::AwaitingReply {
self.status = MutationStatus::Uncertain;
self.request = None;
}
self.status
}
pub fn into_request<Query>(self) -> TableRequest<Query, Command> {
TableRequest::Mutate {
receipt: self.receipt,
command: self.command,
guarantee: self.guarantee,
}
}
}
impl<Command, Value, Outcome, Refusal> StepInputOf<PendingMutation<Command>>
for ReceiveMutation<'_, Value, Outcome, Refusal>
{
type Outcome = Result<MutationStatus, MutationReceiveError>;
fn step(self, pending: &mut PendingMutation<Command>) -> Self::Outcome {
if pending.status != MutationStatus::AwaitingReply || pending.request != Some(self.id) {
return Err(MutationReceiveError::Unexpected);
}
let status = match self.reply {
Ok(TableReply::Mutation(answer)) => match answer {
MutationAnswer::Pending { receipt, .. } => {
if *receipt != pending.receipt {
return Err(MutationReceiveError::WrongReceipt);
}
MutationStatus::AwaitingReply
}
MutationAnswer::Confirmed {
receipt,
established,
..
} => {
if *receipt != pending.receipt {
return Err(MutationReceiveError::WrongReceipt);
}
let sufficient = match pending.guarantee {
Guarantee::Accepted => true,
Guarantee::LocallyDurable => matches!(
established,
Guarantee::LocallyDurable | Guarantee::ReplicatedDurable
),
Guarantee::ReplicatedDurable => {
*established == Guarantee::ReplicatedDurable
}
};
if !sufficient {
return Err(MutationReceiveError::InsufficientGuarantee);
}
MutationStatus::Confirmed
}
MutationAnswer::Unknown | MutationAnswer::Unavailable(_) => {
MutationStatus::Uncertain
}
},
Err(_) => MutationStatus::Uncertain,
Ok(TableReply::Read(_) | TableReply::Retired(_)) => {
return Err(MutationReceiveError::Unexpected);
}
};
pending.status = status;
if status != MutationStatus::AwaitingReply {
pending.request = None;
}
Ok(status)
}
}