pub struct EventLog<E>where
E: Context + BufferPooler,{ /* private fields */ }Expand description
An append-only, ordered log of conversation Events.
Generic over a commonware_storage::Context so the same code runs on the
tokio backend in production and the deterministic backend in tests. See the
crate-level docs for the ordering contract and runtime-coexistence notes.
Implementations§
Source§impl<E> EventLog<E>where
E: Context + BufferPooler,
impl<E> EventLog<E>where
E: Context + BufferPooler,
Sourcepub async fn open(
context: E,
config: EventLogConfig,
) -> Result<Self, EventLogError>
pub async fn open( context: E, config: EventLogConfig, ) -> Result<Self, EventLogError>
Open (creating if absent, recovering if present) the event log for a
conversation on the given runtime context.
On open the journal replays only its final section to recover the exact append size, and self-heals any data/offset divergence left by a crash.
§Errors
Returns EventLogError::Journal if the underlying storage fails to
initialize or recover the journal.
Sourcepub async fn destroy(self) -> Result<(), EventLogError>
pub async fn destroy(self) -> Result<(), EventLogError>
Destroy the log: consume the handle and REMOVE the partition’s
underlying blobs (data + offsets) from storage. The erasure primitive
(#216): after this, a fresh EventLog::open of the same partition
starts empty.
Deliberately consuming — a destroyed log has no valid further operation, and the caller must drop every other handle first (the control plane’s host serializes this through its single command loop and evicts its cache entry before destroying).
§Errors
Returns EventLogError::Journal if the underlying blob removal
fails.
Sourcepub async fn append(&self, event: &Event) -> Result<u64, EventLogError>
pub async fn append(&self, event: &Event) -> Result<u64, EventLogError>
Append a single event, returning the position the journal assigned it.
Positions are strictly increasing from 0 and define replay order. The
caller must append in conversation order (turn, then seq within a turn)
for replay to reflect that order.
Takes &self: EventLog’s own lock (see the struct-level doc)
recovers a shared reference over the journal’s &mut self ops.
Appends are buffered for durability; call EventLog::commit (or
EventLog::sync) to guarantee they survive a crash.
§Errors
Returns EventLogError::Journal if the item cannot be encoded or the
underlying storage write fails.
Sourcepub async fn len(&self) -> u64
pub async fn len(&self) -> u64
Number of events appended to the log (the position the next append will receive). Not reduced by pruning.
Sourcepub async fn replay_with_positions(
&self,
) -> Result<Vec<(u64, Event)>, EventLogError>
pub async fn replay_with_positions( &self, ) -> Result<Vec<(u64, Event)>, EventLogError>
Replay every event in append order, each paired with its position.
The returned Vec is ordered by position ascending (0, 1, 2, …),
which is conversation order. Each tuple is (position, event).
This collects the full log into memory; it is intended for rebuilding
in-memory conversation state on resume. For very large logs a streaming
variant could be added later (the journal exposes a Stream), but the
foundational API materializes for simplicity.
§Errors
Returns EventLogError::Journal if the journal cannot start the replay
stream or if decoding any stored event fails.
Sourcepub async fn replay_with_positions_bounded(
&self,
max_bytes: u64,
) -> Result<BoundedReplay, EventLogError>
pub async fn replay_with_positions_bounded( &self, max_bytes: u64, ) -> Result<BoundedReplay, EventLogError>
Replay every event in append order, each paired with its position —
same as EventLog::replay_with_positions — but STOP pulling from
the underlying replay stream the instant the cumulative payload bytes
read so far exceed max_bytes.
This is issue #1541’s early-abort primitive: EventLog::replay_with_positions
always drains the whole stream into one Vec before any caller can
check its size, so a budget checked only after that call returns has
already paid the full allocation cost it meant to avoid. This method
instead checks the running byte total INSIDE the same loop that pulls
from the stream, so the returned Vec never grows past max_bytes
plus one event’s own payload size (the one event whose read tips the
budget over is kept, then the loop breaks — the rest of the
partition, however large, is never fetched from the journal).
§Errors
Returns EventLogError::Journal if the journal cannot start the
replay stream or if decoding any stored item fails before the budget
trips.
Sourcepub async fn replay_from_with_positions(
&self,
start: u64,
) -> Result<Vec<(u64, Event)>, EventLogError>
pub async fn replay_from_with_positions( &self, start: u64, ) -> Result<Vec<(u64, Event)>, EventLogError>
Replay events in append order starting at position start, each paired
with its position.
The journal is position-indexed, so resuming at an offset is cheap — the
reader seeks to start rather than scanning from zero. This is what lets
a caller replay only the tail since a durable checkpoint instead of
re-reading the whole partition every time. start is clamped up to the
pruning boundary, and a start at or past the end yields an empty Vec.
Returned tuples are (position, event) for positions in
[max(start, bounds.start), len), ascending.
§Errors
Returns EventLogError::Journal if the journal cannot start the replay
stream or if decoding any stored event fails.
Sourcepub async fn replay_from_with_positions_bounded(
&self,
start: u64,
max_bytes: u64,
) -> Result<BoundedReplay, EventLogError>
pub async fn replay_from_with_positions_bounded( &self, start: u64, max_bytes: u64, ) -> Result<BoundedReplay, EventLogError>
Replay events in append order starting at position start, each
paired with its position — same resume semantics as
EventLog::replay_from_with_positions — but STOP pulling from the
underlying replay stream the instant the cumulative payload bytes read
so far exceed max_bytes, the same early-abort mechanic
EventLog::replay_with_positions_bounded applies to a replay from
the very start.
This is the combined primitive neither of the other two replay
methods can express alone: EventLog::replay_with_positions_bounded
bounds bytes but always starts at position 0, and
EventLog::replay_from_with_positions resumes at start but has no
byte cap, so a partition whose TAIL (the part after a caller-held
watermark) is itself large could still be materialized in full before
any caller ever gets a chance to reject it. This method closes that
gap: a caller resuming from its own cached watermark (polyc_query’s
per-partition decode cache is the first consumer) gets the same
mid-stream budget enforcement a fresh replay already had, without
paying to re-read anything before start.
start is clamped up to the partition’s own pruning boundary exactly
as EventLog::replay_from_with_positions does, and a start at or
past the partition’s end yields an empty, budget_exceeded: false
BoundedReplay rather than an error.
§Errors
Returns EventLogError::Journal if the journal cannot start the
replay stream or if decoding any stored item fails before the budget
trips.
Sourcepub async fn replay_range_with_positions_bounded(
&self,
start: u64,
end: u64,
max_bytes: u64,
) -> Result<BoundedReplay, EventLogError>
pub async fn replay_range_with_positions_bounded( &self, start: u64, end: u64, max_bytes: u64, ) -> Result<BoundedReplay, EventLogError>
Replay events in [start, end) — end EXCLUSIVE — each paired with its
position, under the same byte cap
EventLog::replay_from_with_positions_bounded applies.
The only replay primitive here that accepts an UPPER bound. Every
other one drains to the journal’s tail: replay_with_positions and
replay_from_with_positions have no cap at all, and the two
_bounded siblings cap BYTES, which stops a large read but cannot
express “these events and no others”. A caller that knows the exact
span it wants — one turn’s events, say, located by a prior index —
otherwise has to replay from start to the tail and discard the
remainder, so the cost of fetching a hit near the beginning of a long
conversation scales with the conversation rather than with the hit.
Both ends are clamped to the partition’s own bounds: start up to the
pruning boundary (as EventLog::replay_from_with_positions does),
end down to the journal’s tail, so an end past the tail reads to
the tail rather than erroring. An empty or inverted range — start
at or past the clamped end — yields an empty,
budget_exceeded: false BoundedReplay, never an error.
The byte cap keeps the same meaning it has on the sibling methods: the
event that trips the budget is INCLUDED, and budget_exceeded is set
so the caller can tell a truncated read from a complete one. A range
that ends before the cap trips returns budget_exceeded: false even
if bytes_read is large, because the range, not the budget, is what
stopped it.
§Errors
Returns EventLogError::Journal if the journal cannot start the
replay stream or if decoding any stored item fails before either bound
stops it.
Sourcepub async fn replay(&self) -> Result<Vec<Event>, EventLogError>
pub async fn replay(&self) -> Result<Vec<Event>, EventLogError>
Replay every event in append order, discarding positions.
Convenience over EventLog::replay_with_positions for callers that
only need the ordered events.
§Errors
Returns EventLogError::Journal on the same conditions as
EventLog::replay_with_positions.
Sourcepub async fn replay_from(&self, start: u64) -> Result<Vec<Event>, EventLogError>
pub async fn replay_from(&self, start: u64) -> Result<Vec<Event>, EventLogError>
Replay events from position start in append order, discarding
positions. Convenience over EventLog::replay_from_with_positions.
§Errors
Returns EventLogError::Journal on the same conditions as
EventLog::replay_from_with_positions.
Sourcepub async fn replay_quarantining(
&self,
) -> Result<(Vec<(u64, Event)>, Vec<QuarantinedItem>), EventLogError>
pub async fn replay_quarantining( &self, ) -> Result<(Vec<(u64, Event)>, Vec<QuarantinedItem>), EventLogError>
Replay every event in append order, skipping any position whose item cannot be decoded rather than aborting the whole replay.
EventLog::replay_with_positions stops at the first bad item (the
backup/DR gap #799 tracks: one corrupted event permanently locks a
conversation out of replay). This reads each position independently
through the journal’s position index — a corrupted item’s neighbors
don’t depend on decoding it — so it recovers everything readable and
reports the rest as QuarantinedItems. This is the primitive a
conversation repair operation uses to drop only the unreadable
event(s) and let the rest of the log replay again; it is otherwise
intended for that recovery path, not routine replay (one read per
position, versus one streamed pass).
§Errors
Returns EventLogError::Journal if the journal cannot report its
own bounds. A per-item decode failure is reported in the returned
quarantine list, never as an Err.
Sourcepub async fn commit(&self) -> Result<(), EventLogError>
pub async fn commit(&self) -> Result<(), EventLogError>
Durably persist all buffered appends, guaranteeing they survive a crash.
Committed appends survive a crash, but the next EventLog::open may
perform recovery work rebuilding the position index from data before
replay is available — EventLog::sync additionally makes the next
open recovery-free.
§Errors
Returns EventLogError::Journal if the underlying flush fails.
Sourcepub async fn sync(&self) -> Result<(), EventLogError>
pub async fn sync(&self) -> Result<(), EventLogError>
Stronger durability than EventLog::commit: persist and guarantee no
recovery work is needed on next open.
§Errors
Returns EventLogError::Journal if the underlying sync fails.
Auto Trait Implementations§
impl<E> !Freeze for EventLog<E>
impl<E> !RefUnwindSafe for EventLog<E>
impl<E> !UnwindSafe for EventLog<E>
impl<E> Send for EventLog<E>
impl<E> Sync for EventLog<E>
impl<E> Unpin for EventLog<E>
impl<E> UnsafeUnpin for EventLog<E>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more