pub trait ProcessEventSink: Send + Sync {
// Required method
fn emit<'life0, 'life1, 'async_trait>(
&'life0 self,
event: &'life1 ProcessEvent,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
Host-facing, best-effort push of each appended process event.
A sink is an optional freshness feed, never a source of truth. The
durable event log (ProcessRegistry::events_after) is the only complete
record; a sink lets a host observe appends promptly without polling, but it
makes no delivery promise.
§Contract
- Best-effort freshness, never truth. [
WatchedProcessRegistry] callsemitafter a successfulappend_event, in that pod’s per-process append order. There is no buffering, no retry, and no delivery guarantee across pod crashes or restarts: an event that was appended durably may never reach the sink (e.g. the pod died between the durable write and the emit). Consumers that need completeness reconcile fromevents_after— the durable log is authoritative — typically at terminal time. - Terminal events are deliberately NOT emitted through the sink.
ProcessRegistry::complete_processappends its terminal event via the inner registry internally, so the decorator never observes it as anappend_eventand never emits it. Do not wait on the sink for completion: terminal observation ridesProcessWorkDriver::await_terminal(see ADR 0016), which reads the durable terminal state. - Emission cannot fail the write.
emitreturns(), so a sink can never fail or roll back an append; the durable write has already committed by the timeemitruns. But the decorator awaitsemitinline on the append path, so a slow sink slows every append. Implementors must return fast: hand any real I/O off to a channel or background task internally rather than blocking insideemit.
§Example: offload to a channel
A sink must return fast, so a real implementation hands each event to a
channel and does its projection/logging on a consumer task. Dropping on a
full channel is the correct best-effort behavior — the durable log, read via
events_after, remains the reconcile source.
use lash_core::{ProcessEvent, ProcessEventSink};
use tokio::sync::mpsc;
struct ChannelSink {
tx: mpsc::Sender<ProcessEvent>,
}
#[async_trait::async_trait]
impl ProcessEventSink for ChannelSink {
async fn emit(&self, event: &ProcessEvent) {
// Non-blocking: drop on a full channel rather than slow the append.
let _ = self.tx.try_send(event.clone());
}
}Required Methods§
Sourcefn emit<'life0, 'life1, 'async_trait>(
&'life0 self,
event: &'life1 ProcessEvent,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn emit<'life0, 'life1, 'async_trait>(
&'life0 self,
event: &'life1 ProcessEvent,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Observe one appended process event. Best-effort; see the trait contract.
Must be fast and non-blocking — offload I/O to a channel/task internally.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".