Skip to main content

Module session

Module session 

Source
Expand description

The device-side sync session — the pump that drives a DeviceLog + OplogJournal pair against a Relay (slice B3 of docs/proposals/multi-device-sync.md).

SyncSession::pump is one reconciliation round, sequenced so the binding contracts from B1/B4 hold by construction, not by caller discipline:

  1. Push — only ops read from (or appended through) the journal are ever handed to the relay, and the journal is fsync’d (OplogJournal::sync) before the push, so an op is journal-durable before it is transmitted (B1 MUST — flush is only the page cache; a power loss in the writeback window would otherwise lose a transmitted op and re-mint its seq into a permanent Fork). A crash that loses the push cursor is harmless — a re-push dedups relay-side on op_id.
  2. Pullpull(since = my per-device seq frontier), then verify before fold (B1 MUST): the union of held + pulled ops must pass verify_log (or verify_anchored against the session’s base checkpoint) before anything is folded or journaled.
  3. Fold durably, then ack — verified remote ops are appended to the journal, advancing self.ops in lockstep (each op enters self.ops the instant its append succeeds, so a mid-loop failure leaves self.ops == journal and the retry re-pull filters the already-journaled ops instead of duplicating them — a duplicate line would DuplicateSeq-brick the next open). The journal is then fsync’d and only then is ack sent. The ack value is derived from the journal-held ops — there is no API to ack anything else, so acking merely-received (un-journaled) state is impossible by construction (B4 MUST). A crash between the fold and the ack leaves the relay’s ack table behind — the safe direction: GC can’t drop what we haven’t acked, and the next pump re-acks.

Idempotence: re-running pump after any crash point re-pushes (relay dedups by op_id), re-pulls (already-held ops are filtered by op_id; re-folding is a no-op), and re-acks (monotone) — the whole round is retry-safe.

Cold bootstrap / straggler re-entry (SyncSession::bootstrap / SyncSession::rebase): when the relay has GC’d past the session’s frontier (RelayError::FrontierTruncated) or the device is brand new, the path is exactly the proposal’s — checkpoint_get()pull(since = checkpoint frontier) → verify → journal rewritten as checkpoint-anchored tail (truncate_to, stamping the truncation marker so the naive load/resume stays fenced — B4 contract 5) → resume_anchored, never DeviceLog::resume. Local ops not covered by the checkpoint — including a returning straggler’s never-pushed writes — are carried into the rebased tail and pushed on the next pump: re-entry is lossless (see the relay module docs for why this is safe under seq-based frontiers).

Structs§

PumpReport
What one SyncSession::pump round did.
SyncSession
A device’s live sync endpoint: its append chain, its durable journal, and the pump. See the module docs for the contract sequencing.

Enums§

SessionError
A sync-session failure.