Repository navigation
v0.26.0+ciris.1 — shed a peer that will not drain, before paying to mask it
Answers the deferred #63 question with the mechanism, now that the canonical
measured it: one peer of 130 taking every one of 34,390 dropped packets in
24 h — ~24/s sustained, no idle hour — while the other 129 dropped none, from
a producer that is legitimate and cannot be upgraded yet.
That is the case a deep absorbing queue handles worst. There is no catching
up, so the queue sits at its cap discarding continuously: the depth buys
nothing and the discard is silent to whatever is producing.
Where this sits
Reticulum puts flow control at Link/Channel and leaves the interface layer
best-effort — BufferFull → drop is correct there, not a bug. So this does
not try to make the interface reliable. It makes the shedding cheap and
visible, and leaves reliability to the layer that owns it.
What it does
- Sheds before masking.
dispatch_actionsmasks a packet — an Ed25519
signature plus an HKDF mask stream — and only then discovers the interface
will not take it, so a pinned queue paid full IFAC crypto for every packet
it was about to discard. The breaker runs before that; a shed packet costs a
state check instead of a signature. - Opens after three refused ticks. Tick granularity, not packet, so
"three tries" means three rounds of being refused rather than three frames
inside one congested burst. The cooldown starts at 500 ms and doubles per
failed probe to a 5 minute ceiling — deliberately the cadence a backed-off
producer should trickle at, so probe and trickle meet instead of beating
against each other. Exactly one packet is spent per half-open probe, and a
success both closes the circuit and resets the cooldown, so a peer that
recovers is not punished for having been unreachable. - Never sheds a proof, in any state. A proof is the delivery confirmation
the producer is waiting on. Drop it and the producer retransmits, raising
the offered load exactly when it is already too high — that is how a
congested link becomes a collapsing one. New data yields; confirmations of
data already accepted do not. - Pushes the signal.
NodeEvent::PeerCongestedis Control class, so the
notice that a peer is being shed cannot itself be dropped under load.
interface_stats()gains per-interfacecircuit_openandshed_packets;
plane_stats()gainsshed_packets_total. Per interface on purpose: #66
had 129 healthy peers beside the one that dropped, and a node-wide total
cannot name which.
Following the reference rather than inventing
Threshold and AIMD shape mirror Channel.py — window +1 per delivery, −1 per
timeout, pow(1.5, tries-1) backoff scaled by RTT and queue depth, teardown
after _max_tries.
The half this is not
The producer side — honouring try_send's Busy rather than the absorbing
send(), and backing off exponentially to a trickle — belongs to the
consumer, and is raised separately on CIRISEdge.
Tests: 11 state-machine cases plus 3 driving dispatch_output end to end
(shed-before-mask, proofs survive an open circuit, recovery reaches the event
stream). std lib 654 passed, core 1864 passed, all CI lanes green.