Technical Queues and Memory - Rylan-Meilutis/SEDSnet GitHub Wiki

Queues and Memory (Technical)

This page documents the bounded queue implementation in src/queue.rs (source) and how it affects router and relay behavior.

BoundedDeque

BoundedDeque<T> is a byte-budgeted queue used by both Router and Relay.

Key properties:

  • Byte budget: each item reports its byte_cost via the ByteCost trait.
  • Hard element cap: capacity never exceeds max_elems, derived from size_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 exactly 1.0 on 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.

Transport backpressure

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.

Shared Queue Budget

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 details

Growth uses a rational multiplier instead of floats:

  • float_to_ratio clamps 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 accounting

ByteCost is used throughout:

  • RouterTxItem::Broadcast / RouterTxItem::ToSide include packet byte cost plus metadata.
  • QueueItem::Packed includes Arc<[u8]> overhead and length.
  • RelayRxItem and RelayTxItem account for their payload sizes.
  • SmallPayload accounts for inline vs heap sizes.

Because ByteCost is approximate, the max_elems hard cap prevents pathological growth.

Router and Relay queues

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 discovery feature 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 discovery feature is enabled.

Configuration knobs

These values are packaged defaults selected at compile time via src/config.rs (source):

  • STARTING_QUEUE_SIZE
  • MAX_QUEUE_BUDGET
  • QUEUE_GROW_STEP
  • MAX_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.

Tuning guidance

  • Increase MAX_QUEUE_BUDGET if you see dropped packets, reliable buffer eviction, or topology eviction warnings under bursty traffic.
  • Increase MAX_RECENT_RX_IDS if you have many duplicates across links. This cache is preallocated, so increasing it reserves more of MAX_QUEUE_BUDGET immediately.
  • Reduce QUEUE_GROW_STEP if you want smaller memory spikes.

Failure modes

  • 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 std builds.
  • 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.
⚠️ **GitHub.com Fallback** ⚠️