fix: per-handler claim lease in ledger, nats max_deliver, pin handler names
This commit is contained in:
@@ -2,15 +2,43 @@ use async_trait::async_trait;
|
||||
|
||||
use crate::{errors::DomainError, events::EventId};
|
||||
|
||||
/// Records which handler has already completed which event, so a redelivered
|
||||
/// event does not re-run handlers that succeeded.
|
||||
/// How long an `in_progress` claim blocks other deliveries of the same
|
||||
/// (event, handler). Past it the claim is presumed dead (crashed worker) and
|
||||
/// the next `begin` takes it over. Must exceed any handler's runtime.
|
||||
pub const HANDLER_LEASE_SECONDS: i64 = 1800;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Begin {
|
||||
/// The caller now holds the claim and must run the handler, then
|
||||
/// `complete` or `release`.
|
||||
Run,
|
||||
/// The handler already finished this event; skip it.
|
||||
AlreadyDone,
|
||||
/// Another delivery holds a live claim; do not run, retry the delivery later.
|
||||
InProgressElsewhere,
|
||||
}
|
||||
|
||||
/// Per-(event, handler) claim ledger that stops redelivered events from
|
||||
/// re-running handlers.
|
||||
///
|
||||
/// Guarantee: two deliveries never run the same handler for the same event
|
||||
/// concurrently (`begin` is atomic across workers). After a crash mid-handler
|
||||
/// the claim lapses after `HANDLER_LEASE_SECONDS` and the handler runs again,
|
||||
/// so delivery is at-least-once and handlers must be idempotent.
|
||||
#[async_trait]
|
||||
pub trait ProcessedEventLedger: Send + Sync {
|
||||
async fn has_processed(&self, id: &EventId, handler: &str) -> Result<bool, DomainError>;
|
||||
/// Atomically claims (event, handler): inserts a claim if there is none, or
|
||||
/// takes over an `in_progress` claim older than the lease; otherwise
|
||||
/// reports the existing state.
|
||||
async fn begin(&self, id: &EventId, handler: &str) -> Result<Begin, DomainError>;
|
||||
|
||||
/// Idempotent: recording an existing pair is not an error.
|
||||
async fn record_processed(&self, id: &EventId, handler: &str) -> Result<(), DomainError>;
|
||||
/// Marks the claim done: the handler will not run for this event again.
|
||||
async fn complete(&self, id: &EventId, handler: &str) -> Result<(), DomainError>;
|
||||
|
||||
/// Returns the number of rows removed.
|
||||
/// Drops an `in_progress` claim so a retry can run immediately.
|
||||
async fn release(&self, id: &EventId, handler: &str) -> Result<(), DomainError>;
|
||||
|
||||
/// Removes rows (done, or abandoned `in_progress`) first recorded before
|
||||
/// `cutoff`. Returns the number removed.
|
||||
async fn delete_older_than(&self, cutoff: chrono::NaiveDateTime) -> Result<u64, DomainError>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user