Skip to main content

Module fill_session

Module fill_session 

Source
Expand description

FillSession — the cache-resident fill-coordination primitive the serve-miss legs share (ADR 038).

§Why this exists

The serve leg must emit ONE coherent whole-range bao verified stream while the pull leg fills the cache incrementally beside it. bao_tree’s async encode_ranges_validated is the right engine — it walks the range pre-order, reading leaf DATA and, for every internal node, the (left, right) hash pair from an Outboard. The data the serve reads from the cache (via ranged export, gated on the present-range watch). But the OUTBOARD it cannot: iroh-blobs keeps a partial blob’s outboard in actor memory and exposes no reader, and a pre-order walk needs proof hashes for not-yet-filled right-spine subtrees (the root’s right_hash is emitted before any right-half data lands) that are NOT derivable from present data.

So the cache keeps its own copy, captured as each range admits (the pull leg already receives those exact proof nodes in the verified upstream bao it ingests, and the cache’s admit_bao_stream captures them via FillSession::capture into a whole-tree buffer). The serve leg reads them through a SessionOutboardReader, whose Outboard::load AWAITS a node that has not been captured yet — racing the registry-wide fill liveness so a fill that dies (or a genuinely missing node after a clean pull) fails the serve rather than hanging.

§The outboard is per-HASH, shared by every fill of that hash

A blob is BLAKE3-addressed, so its tree geometry — and thus every captured (left, right) node — is a property of the HASH, not of any single pull. When two serve-misses want overlapping bytes of one hash, one opens a pull for its remainder and the other for its own; both admit into the SAME store and each admit captures proof nodes into the ONE per-hash HashOutboard. Because attach ∪ remainder = R by construction, the two pulls together capture every proof node a coherent encode of R needs, into that single buffer. A serve leg reading R therefore does not care which pull filled what — it reads the shared per-hash outboard and the source-agnostic present-range watch.

The one thing that must generalize past a single pull is termination: “await this node, or fail” has to consult ALL live fills whose covered ranges intersect the node’s byte span, not one pull’s terminal signal. FillSession::range_still_live (backed by FillRegistry::range_still_live) answers exactly that — it settles a parked reader once no live session’s covered still intersects the range, which is the N-fill generalization of the single-pull failure race. A settled read is served from the local store when the store holds what it needs, and fails otherwise; it never hangs.

§Ownership seam (cache owns the structure, the node drives it)

The pull leg (paid peer or own-origin) is node-side and alone knows when/why it terminated. So the session exposes FillSession::mark_ended; the node’s pull leg calls it on every exit. The cache owns the DATA STRUCTURE; the node DRIVES it. FillError is a flattened terminal message (anyhow::Error is not Clone).

Structs§

Demand
A fill’s serve demand: one standing demand per serve leg that has run out of encoded bytes while its encode waits on content this fill produces.
DemandSlot
One serve leg’s handle on the serve demand of its blob’s live fills. Minted by FillSession::demand_slot.
DownstreamWatch
A pull leg’s view of its session’s two downstream frontiers: the paid frontier (FillSession::served_frontier) and the serve demand (FillSession::serve_demand). Read-only and cheap to clone, so a pull on its own runtime holds an owned copy. Minted by FillSession::downstream_watch.
FillError
A flattened terminal error for a fill: the node’s pull leg records why its pull ended, and the serve-side SessionOutboardReader / data reader surface it. A String message rather than anyhow::Error because the outcome must be Clone (every observer reads it) and anyhow::Error is not.
FillRegistry
Range-aware coalescing registry: the live in-flight fills, keyed by hash, plus a per-hash captured outboard. A serve-miss consults Self::claim to split its range into an attach (covered by a running pull) and a remainder (fetch it), and Self::range_still_live answers a parked reader’s “keep awaiting or fail?”. Purely synchronous range math under a std Mutex; the lock is never held across an await.
FillSession
One in-flight fill of a total_bytes-byte blob rooted at root.
Frontier
A forward-only content frontier shared between a fill’s serve legs and its pull.
HashOutboard
The captured whole-tree outboard for ONE hash, shared by every FillSession that fills that hash. A blob’s tree geometry is fixed by its BLAKE3 root, so a node captured by any pull — of any range, from any source — is valid for every serve leg reading that hash. The registry keys exactly one of these per hash and every session for the hash references it, which is what makes partial-overlap proof-sourcing free: pull A (admitting attach) and pull B (admitting remainder) capture into the same buffer, and a serve leg for R = attach ∪ remainder reads every node either supplied.
ObserverLease
RAII observer handle on a FillSession. One is minted for the pull owner and one per attached serve leg (FillRegistry::claim). Releasing it decrements the session’s observer count; the last-out release, if the pull is still running, cancels the pull so a fill no client is waiting on stops ingesting (#1610) — and hands back the session’s parked pull-thread handle so THAT caller joins it off-task (the owner-join hand-off: whoever leaves LAST joins the pull, freeing an owner whose own client finished first).
SessionOutboardReader
Serve-side Outboard over a hash’s shared HashOutboard. Its Outboard::load awaits a not-yet-captured node, racing the registry-wide fill liveness for the no-hang guarantee: once no live fill still covers the node’s byte range, it reads the node from the local store when the reader carries a store fallback (Self::with_store_fallback), and fails when that cannot supply it. Minted via FillSession::outboard_reader.

Enums§

FillClaim
The atomic outcome of FillRegistry::claim: a serve-miss either coalesces wholly onto live pulls (Attach), owns a fresh pull for its whole range (Owner), or — on a partial overlap — owns a fresh pull for its non-overlapping REMAINDER while attaching to a live sibling for the overlap (Mixed). Every variant carries the FillSessions and ObserverLeases the caller holds for the life of its serve leg. claim decides the split AND registers any new owner session under ONE map-lock acquisition, so two concurrent fresh misses for the same blob cannot both open a pull.