Skip to content

spsc-fat: backpressure-preserving pool-exhaustion mode (keep the hardware-ARQ contract under consumer stalls) #361

Description

@josephnef

Context (measured, #359)

The arq_e2e bench proved both directions of the ACK contract on the 8812EU: with the default async ring, receiver-internal congestion (150 ms consumer freezes against a ~2.3 k fps flood) makes the chip decline the ACK — 14,214/14,214 forced drops reported ok=0, retried by the peer, 0 ACKed-but-undelivered in ~460k ok'd frames. With spsc-fat, the same stalls exhaust the pool and the pump drops the payload after chip admission+ACK: 5,468–5,739 ACKed-but-undelivered per run (4 replications, now counted by pool_dropped in rx.ring).

So spsc-fat's never-block invariant, implemented as drop-on-exhaustion, converts ARQ-recoverable loss into silent loss the TX peer logs as delivered. Users enable spsc-fat for smoothness (it eliminates async's retry storms during stalls) and silently trade away the ARQ contract at the exhaustion cliff.

Proposal

A pool-exhaustion policy: backpressure (new) vs drop (current).

In backpressure mode, on pool exhaustion the pump does not re-arm the completed URB — armed falls, the chip FIFO fills, the chip stops ACKing, and the loss becomes ARQ-visible exactly as in async mode — while the pump itself stays never-blocking and the consumer keeps its own thread. Absorption up to the pool size, honesty past it.

Steps

  1. DeviceConfig::Rx::pool_exhaust (drop|backpressure) + DEVOURER_RX_POOL_EXHAUST in env_config. Default proposal: backpressure (contract-preserving, no-silent-caps); drop stays for latency-over-integrity consumers — maintainer call on the default.
  2. UsbTransport spsc branch: on fresh == nullptr in backpressure mode, park the transfer (mutex-guarded stack) instead of re-arming with the received buffer; the consumer's buffer-return path re-arms parked transfers (libusb submit is thread-safe from any thread). Count pool_stalls instead of pool_dropped on this path.
  3. Telemetry: rx.ring gains pool_stalls; docs/logging.md row updated; the spsc-fat mode comment's ARQ caveat updated to describe both policies.
  4. Teardown: parked transfers must be cancelled/reaped like armed ones — cover with tests/teardown_gen_sanity.sh + tests/tx_teardown_asan.sh.

Validation / acceptance

  • tests/arq_e2e_delivery.sh escalation arm (DUT_SPIN_US=40 DUT_STALL_MS=150 DUT_STALL_EVERY=3000 DRONE_GAP_US=250 DUT_RX_MODE=spsc-fat) ×3 replications in backpressure mode: acked_undelivered = 0, pool_stalls > 0, drone-side ok% dips toward the async-arm profile (~93%) during stalls.
  • No-stall baseline: au = 0 and throughput parity with drop mode.
  • Drop mode unchanged and still counted (pool_dropped).

Refs: #359 (bench + counter), docs/logging.md rx.ring row, CLAUDE.md ACK-horizon paragraph.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions