Skip to content

feat: PROVES USP radio integration (collapses PRs #1–#3) - #5

Closed
Mikefly123 wants to merge 7 commits into
pcr-usp-basefrom
feat/proves-usp-radio
Closed

feat: PROVES USP radio integration (collapses PRs #1–#3)#5
Mikefly123 wants to merge 7 commits into
pcr-usp-basefrom
feat/proves-usp-radio

Conversation

@Mikefly123

Copy link
Copy Markdown

Purpose

Collapses the three open fix PRs against pcr-usp-base into a single integration branch so the superproject's .gitmodules lib/fprime submodule pin has one commit to point at, instead of juggling three separate branches.

This branch is a straight --no-ff merge of the three PRs in order, with no additional changes. The constituent PRs stay open for traceability / individual review history; this PR is not intended to replace them, only to provide a mergeable integration point for the submodule pin.

Constituent PRs (stay open — do not close)

Related

Verification

Diff of this branch vs base 8a62e455 matches the union of the reference patches (fprime-com-aggregator-bounded-timeout.patch, fprime-sched-tick-drop.patch, fprime-tlmpacketizer-i16-offset.patch) plus the expected UT-file additions from PR #1 (Svc/ComAggregator/test/ut/ComAggregatorTestMain.cpp, ComAggregatorTester.cpp, ComAggregatorTester.hpp). No merge conflicts; no unaccounted-for deltas.

🤖 Generated with Claude Code

Mikefly123 and others added 7 commits July 23, 2026 18:41
timeout_handler forwards a periodic (10 Hz in our configuration) timeout
signal into the component's internal state-machine queue. PR nasa#4402 added
an m_allow_timeout guard that suppresses this signal while the state
machine is in WAIT_STATUS, preventing queue overflow when a com
transmission is slow. That guard does not cover the FILL state
(m_allow_timeout == true): if the component's own dispatch thread stalls
(e.g. downstream backpressure on a serial/radio link, or thread
starvation under system load) for longer than queue_depth / timeout_rate,
ticks keep arriving and filling the depth-bounded queue while nothing
drains it. Once full, the autocoded signal-send in the state machine
(aggregationMachine_sendSignal timeout) hits its FW_ASSERT on
QUEUE_FULL, which is fatal in flight configurations.

fill and status are each flow-controlled to at most one in-flight
message by the com protocol, so timeout is the only unbounded producer
feeding this queue. Since timeout ticks are periodic and idempotent (a
missed tick is retried on the next cycle), skip forwarding the signal
whenever the queue does not have headroom for it plus the (at most one
each) in-flight fill/status signals, rather than only checking
m_allow_timeout.

Observed on hardware (PROVES CubeSat, RP2350/Zephyr, F' v3.1.1) during
HWIL soak testing: a stalled dispatch thread (CDC-ACM host-side USB
stall) let 10 Hz ticks fill a depth-15 queue in ~1.5 s, triggering the
queue-full assert and a hard reboot.

Generative AI (Claude, Anthropic) was used to help root-cause this
defect during HWIL debugging and to draft this change; disclosed per
AI_POLICY.md in the upstream pull request description.
The existing test_timeout_overflow_prevention only exercises the
WAIT_STATUS-adjacent path: it drives a comStatusIn failure first, so
m_allow_timeout is already false during the flood. It does not put the
state machine in FILL and stall dispatch, so it does not cover the
FILL-state gap closed by the previous commit.

Add test_timeout_overflow_prevention_fill_state(), the complement of the
existing test: run test_initial() to land in FILL
(m_allow_timeout == true) with an empty aggregation buffer and empty
queue, then invoke timeout 2 * TEST_INSTANCE_QUEUE_DEPTH times
back-to-back with no intervening drain -- simulating a stalled dispatch
thread receiving periodic sched ticks. After every invocation, assert
the queue never reaches full depth. Then confirm the flood had no side
effects (dataOut never fired), confirm recovery via
dispatchCurrentMessages once the simulated stall clears, and confirm
normal fill/timeout operation resumes cleanly afterward.

Note: dispatchOne's underlying doDispatch() issues a blocking
Os::Queue::receive with no "queue empty" return path of its own
(MSG_DISPATCH_EMPTY is only produced by dispatchCurrentMessages, which
snapshots the message count up front). Draining must use
dispatchCurrentMessages rather than looping dispatchOne until an empty
status, which would hang.

Verified: with the bounded-timeout fix applied, all 11 ComAggregator UTs
pass (including the new test). With the fix reverted, the new test fails
deterministically at the 20th flood iteration
(getMessagesAvailable() == TEST_INSTANCE_QUEUE_DEPTH == 20), confirming
it exercises the regression this fix addresses.

Generative AI (Claude, Anthropic) was used to help draft this test
during HWIL follow-up work; disclosed per AI_POLICY.md in the upstream
pull request description.
Adds the drop queue-full annotation to two classes of periodic async
input ports across Svc, where none currently exists:

1. The Svc.Sched async input on active components that receive a rate-
   group tick but did not have drop set: CmdSequencer.schedIn,
   CmdDispatcher.run, TlmChan.Run, TlmPacketizer.Run, FileDownlink.Run,
   BufferLogger.schedIn, DpManager.schedIn, DpWriter.schedIn.
2. PingIn/pingIn async ports across Svc, including
   ActiveRateGroup.PingIn and FpySequencer.pingIn (the latter previously
   priority 10 assert, with an open TODO questioning priority/behavior),
   plus two ports added upstream since this defect class was first
   scoped: BufferAccumulator.pingIn and FileDispatcher.pingIn.

This is the same defect class fixed for ComAggregator (see companion
PR): any active component fed periodic ticks by a rate group can, if its
own dispatch thread stalls longer than queue_depth / tick_rate,
accumulate enough queued ticks to trip the autocoded FW_ASSERT on
queue-full -- turning a transient stall into an unrecoverable
FATAL/reboot.

Captured on hardware (PROVES CubeSat, RP2350/Zephyr) during HWIL soak
testing, in two independently reproduced instances:
- CmdSequencer::schedIn_handlerBase (10 Hz sched tick) hit the identical
  QUEUE_FULL assert as the ComAggregator case, while a sequencer's
  dispatch thread was stalled.
- ActiveRateGroup::PingIn_handlerBase hit the same assert via
  Svc::Health's 1 Hz ping -- the health-check mechanism itself killed
  the board. Health's ping-timeout policy exists specifically to detect
  an unresponsive component and react gracefully; asserting on the ping
  enqueue short-circuits that design.

Both classes of tick (rate-group sched, and ping) are periodic and
idempotent by construction -- a dropped tick is simply retried on the
next cycle -- so drop is behaviorally safe. Upstream already uses drop
for exactly this reason on ComQueue.run and ActiveRateGroup.CycleIn;
this extends the same reasoning to the remaining periodic producers
that were missed.

Related to issue nasa#4195 ("Add a 'Drop But Warn' on Queue Full"), which is
in the same design space but currently leans assert-by-default; this
change is narrower in scope (periodic/idempotent producers only,
per-port opt-in via the existing drop annotation, no new mechanism).

This is an fpp-only annotation change consumed by the autocoder; no new
C++ logic. A reviewer sweep of existing per-component UTs that assert on
QUEUE_FULL behavior for these ports is recommended before merge.

Generative AI (Claude, Anthropic) was used to help root-cause this
defect class during HWIL debugging (two independently captured hardware
instances of the same assert) and to enumerate affected ports; disclosed
per AI_POLICY.md in the upstream pull request description.
…gnedSizeType to I16

TlmEntry stores FwSignedSizeType packetOffset[MAX_PACKETIZER_PACKETS] per
channel. The value is a byte offset into a ComBuffer-sized packet (bounded
by FW_COM_BUFFER_MAX_SIZE, typically a few hundred bytes) or the -1
'not in this packet' sentinel -- a 64-bit signed type per slot is 4-8x
oversized. Shrink to I16 (keeps the -1 sentinel; supports packet offsets
to 32 KB) with a config-time FW_ASSERT range guard at the single
assignment site in setPacketList.

Validated on a real CubeSat deployment (MAX_PACKETIZER_CHANNELS=202,
MAX_PACKETIZER_PACKETS=22, RP2350/Zephyr, v5e flight board):
- -27,472 B BSS (60,480 -> 33,008 B, -45%) in CdhCore::tlmSend
- Svc/TlmPacketizer UTs: 12/12 passed (1 pre-existing skip)
- HWIL-verified: clean boot, NO_OP round-trip, packetized telemetry
  decodes bit-correct, SEND_PKT works; live heap walk matches nm
  prediction exactly (free-at-boot 11,504 -> 38,976 B)

Tracked by Open-Source-Space-Foundation/proves-core-reference#469.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 76cbf243-c192-4d7b-8cd7-480c368fbbc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Mikefly123

Copy link
Copy Markdown
Author

Superseded by native stacked PR chain #1#2#3 (stack nasa#6, linked via gh stack link), each layer now independently reviewable and byte-equivalent to this branch's tree (baf163f). Closing this PR; branch feat/proves-usp-radio is retained as-is since proves-core-reference's .gitmodules still pins it at baf163f.

@Mikefly123 Mikefly123 closed this Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant