Expand description
Network fanout acknowledgement policy and result types.
Implements Gun.js ask-pattern semantics for multi-peer replication: a
requester sends a Message::Put to the
router, the router registers the put as a quorum-tracked write via
Message::RegisterQuorum,
the router fans the put out to peers, and tracks the per-peer acks
until an AckPolicy quorum is satisfied.
§Wire format (Gun.js compatible)
Peer acks reuse the existing Put { in_response_to: Some(put_id), .. }
wire — the @ field in serialized JSON. Quorum completion is signalled
by a __quorum_met__ sentinel in the reply’s updated_nodes, with
the ack count as the value. This mirrors the existing _ack/_err
sentinel convention used by Node::put and
Node::batch_put, so callers can use the
same drain plumbing.
§Reserved sentinel prefix
__beam__ is the reserved prefix for wire-level sentinels. Existing:
__beam_replay_complete__— emitted bymap()replay to signal drain complete
New:
__quorum_met__— emitted by Router when quorum threshold is reached
The __ prefix is filtered during normal data iteration (see
Node::handle_put reserved-prefix handling), so these sentinels never
collide with user data.
§Lifecycle
Node::put_quorum(value, policy)
├── build Put, register oneshot in pending_puts
├── send Message::RegisterQuorum { put_id, requester, policy } to Router
├── send Message::Put(put) to Router
│ ↓
│ Router creates internal QuorumEntry for put_id
│ Router::handle_put_relay fans out to peers (same as fire-and-forget)
│ ↓
│ Each peer eventually replies with Put { @: put_id, .. }
│ ↓
│ Router::handle_put sees Put.@, finds QuorumEntry, increments counter
│ When counter >= policy.quorum → Router sends reply back to requester:
│ Put { @: put_id, updated_nodes: { "__quorum_met__": ack_count } }
└── requester's oneshot resolves with ReplicationStatus§Why a sentinel, not a new Result variant
The codebase has converged on sentinel-drain as the canonical ack
pattern (see feat/beam-redux-async-ack-and-drain branch):
_ack/_errsentinels for storage commit confirmation__beam_replay_complete__sentinel for replay drain
Adding __quorum_met__ keeps the drain plumbing DRY — the same
pending_puts: Arc<RwLock<HashMap<String, oneshot::Sender<...>>>> map
and tokio::time::timeout envelope that Node::put
uses are reused for Node::put_quorum.
Only the decoder differs: instead of looking for _ack/_err,
put_quorum looks for __quorum_met__.
§Quorum policies
AckPolicy::any— first ack wins (Gun.js default, fastest)AckPolicy::for_peer_count— ⌈N/2⌉ majority (Raft/Dynamo style)AckPolicy::all— every fan-out target must ack
§Timeout
Default timeout matches Gun.js lack = 9000ms. Configurable via
AckPolicy::with_timeout. On timeout the requester’s oneshot resolves
with Err("put_quorum timed out") and the Router’s internal
QuorumEntry is reaped lazily (removed on next access by another put
for the same id, or by the Router’s periodic cleanup if added later).
§Design constraints
- No new Message variant for the ack wire — reuses
Put { in_response_to, .. }for ack routing - One new Message variant:
RegisterQuorum— minimal struct with(put_id, requester_addr, policy); used by the Router to create theQuorumEntrybefore fan-out - Sentinel-driven completion —
__quorum_met__inupdated_nodes, matching the_ack/_errconvention - No new dependency — uses existing
std,tokio
Structs§
- AckPolicy
- Policy controlling how many peer acks satisfy a
put_quorumrequest. - Replication
Status - Result of a successful
put_quorumrequest.
Constants§
- DEFAULT_
QUORUM_ TIMEOUT - Default timeout for quorum requests, matching Gun.js
lack = 9000ms. - QUORUM_
MET_ SENTINEL - Reserved sentinel key emitted by the Router when quorum threshold is met.