pub struct DedupCache { /* private fields */ }Expand description
Haematite-backed idempotency-key cache for the lightweight dedup contract.
Clones share one in-flight index (dedup/in_flight.rs). The index assumes
it is the only one kept for its namespace in the process, as the store
assumes one writing process: a second cache built over the same namespace
is a second process’s view, as a restart would be.
Implementations§
Source§impl DedupCache
impl DedupCache
Sourcepub async fn in_flight_claims(
&self,
) -> Result<Vec<InFlightClaim>, DurabilityError>
pub async fn in_flight_claims( &self, ) -> Result<Vec<InFlightClaim>, DurabilityError>
The claims in flight in this namespace, each with its claim time and the evidence it carries, in key order, read from the in-flight index and never by walking the namespace (except once, on the first start over a store written before the index existed).
§Errors
Refuses an unreadable index or key stream, or a malformed record; never answers a partial list.
Sourcepub async fn record_empty_start(&self) -> Result<(), DurabilityError>
pub async fn record_empty_start(&self) -> Result<(), DurabilityError>
Records the start of an index begun empty (a cache opened with
DedupCache::new_empty) whose start is not yet recorded: one
compare-and-set of the index start key from 0 to 1, after which a
later process opening the namespace with DedupCache::new reads
the index and never walks the namespace. The first claim calls this
before it writes; the start that created the store calls it earlier
(DUR-023), so a namespace nothing is ever claimed in is not mistaken
at its next start for a store written before the index existed and
walked, shard by shard, to find nothing.
Makes no store call for a cache opened with DedupCache::new, for
an index whose start is already recorded, or on a second call. Another
join or another start recording the same start first counts as
recorded.
§Errors
Propagates a store error from the compare-and-set other than the cursor regression that says it was recorded first.
Source§impl DedupCache
impl DedupCache
Sourcepub async fn claim_with_evidence(
&self,
key: &str,
evidence: ClaimEvidence,
timestamp_millis: u64,
) -> Result<DedupDecision, DurabilityError>
pub async fn claim_with_evidence( &self, key: &str, evidence: ClaimEvidence, timestamp_millis: u64, ) -> Result<DedupDecision, DurabilityError>
Claim a producer key and persist where its row would be in one append.
The key is first named in the namespace’s in-flight index
(dedup/in_flight.rs), so a start finds the claim without walking the
namespace; a crash between the two writes leaves an index row that the
next start drops, never a claim the index does not name.
§Errors
Propagates storage and codec failures; conflicting keys retain the normal dedup collision refusal. A failed write must be resolved before publishing.
Source§impl DedupCache
impl DedupCache
Sourcepub fn new(store: Arc<dyn DurableStore>, namespace: impl Into<String>) -> Self
pub fn new(store: Arc<dyn DurableStore>, namespace: impl Into<String>) -> Self
Creates a dedup cache over the given durable store and namespace prefix.
Sourcepub fn new_empty(
store: Arc<dyn DurableStore>,
namespace: impl Into<String>,
) -> Self
pub fn new_empty( store: Arc<dyn DurableStore>, namespace: impl Into<String>, ) -> Self
Creates a dedup cache over a namespace nothing has written to: one in a store this process created, or one no earlier process used.
Its in-flight index starts empty without reading the store, so
Self::in_flight_claims reads nothing and no claim walks the
namespace. Self::record_empty_start, or failing that the first
claim, records the index start before anything is written, so a later
process opening the namespace with Self::new reads the index and
never walks it. A namespace that may hold claims is opened with
Self::new.
Sourcepub fn stream_key_for(&self, idempotency_key: &str) -> String
pub fn stream_key_for(&self, idempotency_key: &str) -> String
Formats the haematite stream key for an idempotency key.
Sourcepub async fn lookup(
&self,
idempotency_key: &str,
) -> Result<Option<DedupDecision>, DurabilityError>
pub async fn lookup( &self, idempotency_key: &str, ) -> Result<Option<DedupDecision>, DurabilityError>
Looks up an existing key without appending a record. A stream of fewer than one page of records is read in one page, as before DUR-008 A12; a longer one is read from its verified recorded head, and one whose head was never recorded is walked once and its head recorded.
§Errors
Propagates store read errors and returns DurabilityError::DedupCollision when
the hashed stream contains a different original idempotency key.
Sourcepub async fn claim_or_get(
&self,
idempotency_key: &str,
timestamp_millis: u64,
) -> Result<DedupDecision, DurabilityError>
pub async fn claim_or_get( &self, idempotency_key: &str, timestamp_millis: u64, ) -> Result<DedupDecision, DurabilityError>
Claims a new key or returns the existing completed/in-flight decision.
Callers must only deliver the message when this returns DedupDecision::Claimed.
§Errors
Propagates store errors and serialization errors. A concurrent first claim that wins the append race is converted into the duplicate completed/in-flight decision.
Sourcepub async fn complete_receipt(
&self,
idempotency_key: &str,
receipt: ProcessingReceipt,
) -> Result<(), DurabilityError>
pub async fn complete_receipt( &self, idempotency_key: &str, receipt: ProcessingReceipt, ) -> Result<(), DurabilityError>
Stores a processing receipt by appending a completed entry for an existing key.
§Errors
Returns DurabilityError::ConfigError when the current system time cannot be encoded.
Returns DurabilityError::DedupCollision when the key is missing or collides with
another original key, and propagates serialization/store append errors.
Sourcepub async fn release_claim(
&self,
idempotency_key: &str,
) -> Result<(), DurabilityError>
pub async fn release_claim( &self, idempotency_key: &str, ) -> Result<(), DurabilityError>
Releases an in-flight claim so the key becomes re-claimable.
Callers use this on the publish failure path: a key was claimed
(DedupDecision::Claimed) but the downstream delivery failed before a
receipt could be recorded, leaving the key dangling DedupDecision::InFlight
forever and permanently suppressing every re-publish. Releasing appends a
tombstone so the next claim succeeds.
This NEVER clobbers a stored receipt: an already-completed key (receipt present) and an absent key are both no-ops, preserving the at-most-once guarantee. Only a current in-flight entry (active, no receipt) is tombstoned.
§Errors
Propagates store read/append and serialization errors. A DurabilityError::SequenceConflict
on the tombstone append is re-checked: if the latest state is now completed
or already a tombstone the release is treated as successful, otherwise the
conflict is propagated.