Skip to main content

ProcessEventSink

Trait ProcessEventSink 

Source
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] calls emit after a successful append_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 from events_after — the durable log is authoritative — typically at terminal time.
  • Terminal events are deliberately NOT emitted through the sink. ProcessRegistry::complete_process appends its terminal event via the inner registry internally, so the decorator never observes it as an append_event and never emits it. Do not wait on the sink for completion: terminal observation rides ProcessWorkDriver::await_terminal (see ADR 0016), which reads the durable terminal state.
  • Emission cannot fail the write. emit returns (), so a sink can never fail or roll back an append; the durable write has already committed by the time emit runs. But the decorator awaits emit inline 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 inside emit.

§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§

Source

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".

Implementors§