fix(ReliableRouter): keep the retry across a duty-cycle rejection - #11477
fix(ReliableRouter): keep the retry across a duty-cycle rejection#11477h3lix1 wants to merge 3 commits into
Conversation
ReliableRouter::send() deliberately leaves the pending record in place when the send comes back DUTY_CYCLE_LIMIT, on the reasoning in its comment that the rejection may clear before the scheduled retry. That never worked. Router::send() aborts the packet on a duty-cycle rejection and NAKs it back to us via abortSendAndNak() -> sendLocal() -> deliverLocal(). deliverLocal() only defers when handleDepth > 0, and ReliableRouter::send() runs with handleDepth 0, so the NAK is delivered inline. sniffReceived() sees an error NAK carrying the packet's id and calls stopRetransmission(), erasing the record before send() ever reaches the line that was supposed to protect it. Skip that stopRetransmission() for a self-addressed DUTY_CYCLE_LIMIT NAK. The error is transient and locally generated, so it is the one NAK that should not be the final word. The client still receives it. Duty cycle applies where dutyCycle < 100: EU_433, EU_868, EU_866, EU_N_868, TH and UA_433.
AuthPipelineRoutingModule::sendAckNak() counted the call and returned. In the firmware the call continues into router->sendLocal(), and a NAK addressed to ourselves reaches deliverLocal(), which delivers inline whenever handleDepth is 0, so ReliableRouter::sniffReceived() acts on it while the original caller is still mid-send. That gap is why test_C14_duty_cycle_limited_reliable_send_remains_pending passed against a carve-out that did not work: with no delivery there was nothing to erase the record, so pendingCount() was 1 for the wrong reason. Drive the loopback for error NAKs. C14 now fails without the accompanying fix with "Expected 1 Was 0", which is the behaviour it was written to assert.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
⚡ Try this PR in the Web FlasherNote Building this pull request… the flash button, badges and supported-board |
c81310f to
c5a23fc
Compare
ReliableRouter::send()deliberately leaves the pending record in place when the send comes backDUTY_CYCLE_LIMIT, on the reasoning in its own comment that the rejection may clear before thescheduled retry:
That never worked.
Router::send()aborts the packet on a duty-cycle rejection and NAKs it back to usthrough
abortSendAndNak()->sendLocal()->deliverLocal().deliverLocal()only defers whenhandleDepth > 0, andReliableRouter::send()runs withhandleDepth0, so the NAK is deliveredinline.
sniffReceived()sees an error NAK carrying the packet's id and callsstopRetransmission(), erasing the record beforesend()ever reaches the line above.So the retry the carve-out exists to preserve is gone by the time the check runs, and a duty-cycle
rejection is final in practice rather than transient.
The fix skips that
stopRetransmission()for a self-addressedDUTY_CYCLE_LIMITNAK. It islocally generated and describes a condition that clears on its own, so it is the one NAK that should
not be the last word. The client still receives it.
Duty cycle applies where
dutyCycle < 100: EU_433, EU_868, EU_866, EU_N_868, TH and UA_433.The test was green for the wrong reason
test_C14_duty_cycle_limited_reliable_send_remains_pendingasserts exactly the behaviour that wasbroken, and passed anyway.
AuthPipelineRoutingModule::sendAckNak()counted the call and returned,so nothing was ever delivered, so nothing erased the record, and
pendingCount() == 1held for areason unrelated to what the test claims to check.
The second commit makes that stub deliver its NAKs the way
sendLocal()does. With the stub fixedand the source change reverted, C14 fails:
which is the assertion it was written to make.
Worth flagging separately: the same synchronous loopback is why
doRetransmissions()can use afreed pending record, which I have raised as its own PR. This one is the narrower behavioural half.
Testing: builds clean on
heltec-v4andseeed-xiao-s3, 169/169 acrosstest_packet_signing,test_nexthop_routingandtest_mqtt. Not exercised on hardware, since it needs an EU-region nodedriven past its duty-cycle budget and my bench is US-region.
🤖 Generated with Claude Code
🤝 Attestations