Technical Queues and Memory - Rylan-Meilutis/SEDSnet GitHub Wiki
This page documents the bounded queue implementation in src/queue.rs (source) and how it affects router and relay behavior.
BoundedDeque<T> is a byte-budgeted queue used by both Router and Relay.
Key properties:
-
Byte budget: each item reports its
byte_costvia theByteCosttrait. -
Hard element cap: capacity never exceeds
max_elems, derived fromsize_of::<T>(). - Eviction policy: evict from the front until a new item fits in the byte budget.
-
Growth policy: multiplicative growth using
QUEUE_GROW_STEP. Set it to exactly1.0on fragmented embedded heaps to retain the startup capacity and evict from a full bounded queue without reallocating under live traffic. - Embedded discovery: no-std nodes retain immutable flash schemas plus a bounded runtime overlay. Discovery snapshots and reassembly require transient storage; an optional allocator-headroom probe can reject work before allocating.
- Pressure recovery: Router and Relay reclaim incomplete side transfers after two seconds of inactivity during queue service and before ingress admission. Small ACKs use reserved headroom so ordinary ingress refusal does not block release of reliable replay buffers. The probe estimates headroom; it cannot guarantee every later allocation succeeds.
This keeps memory use bounded and avoids unbounded VecDeque growth in embedded builds.
A side TX callback returning SEDS_IO in C (Rust TelemetryError::Io("side tx busy"))
refuses the frame temporarily. Router and Relay retain refused work for retry during
later queue processing, including Relay's combined RX/TX service loop. Other callback
errors are permanent failures.
Returning SEDS_OK transfers responsibility for delivery to the transport. A driver
must transmit or copy the borrowed callback data before returning, preserve accepted
frames, and refuse new work when its own queue is full. SEDSnet cannot recover a frame
that a driver accepts and silently discards. Retry retention still operates within the
configured queue limits; it does not make overload lossless.
MAX_QUEUE_BUDGET is the overall router/relay queue budget, not a separate allowance for each
internal queue.
The budget is shared dynamically across:
- router/relay RX queues
- router/relay TX queues
- relay replay queues
- recent packet ID caches used for dedupe; these preallocate
min(MAX_RECENT_RX_IDS * sizeof(u64), MAX_QUEUE_BUDGET)bytes and reserve that amount immediately - ordered reliable out-of-order receive buffers
- reliable retransmit/replay state
- learned discovery route and topology state
- runtime schema names, descriptions, endpoint lists, and discovery snapshots
If TX is busy and RX is quiet, TX can use most of the budget that is not already reserved by the
recent-ID cache. If RX later becomes the pressure point, RX can take available budget back. When
multiple areas fill at the same time, eviction comes from the largest queue-backed area first so the
total stays under MAX_QUEUE_BUDGET.
Discovery topology also counts against this budget. In std builds, the router/relay emits a
warning when discovery/topology entries have to be evicted because the shared queue budget is
exhausted.
Runtime JSON schema files are decoded incrementally through a 512-byte default read buffer. The
buffer can be changed at build time with SCHEMA_JSON_CHUNK_BYTES; no_std builds use generated
flash-resident schema definitions and allocate no JSON file buffer. JSON input and retained schema
are both bounded, and failed loads roll back their additions. Owned schema metadata is freed when
removed or replaced.
Growth uses a rational multiplier instead of floats:
-
float_to_ratioclamps the multiplier (1.01 to 16.0). - Capacity growth is
ceil(cap * grow_num / grow_den). - If the queue is at its hard cap, it evicts instead of growing.
ByteCost is used throughout:
-
RouterTxItem::Broadcast/RouterTxItem::ToSideinclude packet byte cost plus metadata. -
QueueItem::PackedincludesArc<[u8]>overhead and length. -
RelayRxItemandRelayTxItemaccount for their payload sizes. -
SmallPayloadaccounts for inline vs heap sizes.
Because ByteCost is approximate, the max_elems hard cap prevents pathological growth.
Router:
- RX queue: holds incoming items before processing.
- TX queue: holds items queued for sending.
- Recent-ID cache: preallocated
BoundedDeque<u64>used for dedupe. - Reliable out-of-order and retransmit buffers.
- Discovery route/topology state when the
discoveryfeature is enabled.
Relay:
- RX queue: items received from any side.
- TX queue: items to send to all other sides.
- Replay queue: explicit reliable retransmits requested by peers.
- Recent-ID cache: similar to Router.
- Reliable out-of-order buffers.
- Discovery route/topology state when the
discoveryfeature is enabled.
These values are packaged defaults selected at compile time via src/config.rs (source):
STARTING_QUEUE_SIZEMAX_QUEUE_BUDGETQUEUE_GROW_STEPMAX_RECENT_RX_IDS
They can be overridden using
build.py with env:KEY=VALUE or
.cargo/config.toml. The active queue budget, starting size, recent-ID capacity, and growth policy
can also be selected per router or relay with RuntimeMemoryConfig (or the matching C/Python
constructor arguments); this does not require rebuilding a packaged host library.
- Increase
MAX_QUEUE_BUDGETif you see dropped packets, reliable buffer eviction, or topology eviction warnings under bursty traffic. - Increase
MAX_RECENT_RX_IDSif you have many duplicates across links. This cache is preallocated, so increasing it reserves more ofMAX_QUEUE_BUDGETimmediately. - Reduce
QUEUE_GROW_STEPif you want smaller memory spikes.
- If a single item exceeds
MAX_QUEUE_BUDGET, it is rejected. - If the shared queue budget is full, older queued state is evicted to make room.
- If discovery topology consumes too much of the budget, older topology entries can be evicted and
a warning is emitted in
stdbuilds. - If JSON input or its resulting schema exceeds its limit, registration fails and leaves the prior registry unchanged.
- If handlers are slow, RX queues may accumulate and evict earlier items.