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.
- Demand
Slot - One serve leg’s handle on the serve demand of its blob’s live fills. Minted by
FillSession::demand_slot. - Downstream
Watch - 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 byFillSession::downstream_watch. - Fill
Error - 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. AStringmessage rather thananyhow::Errorbecause the outcome must beClone(every observer reads it) andanyhow::Erroris not. - Fill
Registry - Range-aware coalescing registry: the live in-flight fills, keyed by hash, plus a
per-hash captured outboard. A serve-miss consults
Self::claimto split its range into an attach (covered by a running pull) and a remainder (fetch it), andSelf::range_still_liveanswers a parked reader’s “keep awaiting or fail?”. Purely synchronous range math under a stdMutex; the lock is never held across an await. - Fill
Session - One in-flight fill of a
total_bytes-byte blob rooted atroot. - Frontier
- A forward-only content frontier shared between a fill’s serve legs and its pull.
- Hash
Outboard - The captured whole-tree outboard for ONE hash, shared by every
FillSessionthat 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 (admittingattach) and pull B (admittingremainder) capture into the same buffer, and a serve leg forR = attach ∪ remainderreads every node either supplied. - Observer
Lease - 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). - Session
Outboard Reader - Serve-side
Outboardover a hash’s sharedHashOutboard. ItsOutboard::loadawaits 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 viaFillSession::outboard_reader.
Enums§
- Fill
Claim - 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 theFillSessions andObserverLeases the caller holds for the life of its serve leg.claimdecides 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.