Release packets the interface declines to send - #11087
Conversation
RadioLibInterface::send() returns ERRNO_SHOULD_RELEASE without releasing when to is NODENUM_BROADCAST_NO_LORA. Callers that discarded the return value leaked a pool slot per packet. Skip rebroadcast of NODENUM_BROADCAST_NO_LORA and honour the return value in NextHopRouter, RoutingModule::sendAckNak and the MQTT ack path.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe routing changes prevent rebroadcasting no-LoRa broadcasts and release packets when send operations request ownership return. ACK paths adopt the same handling, with tests covering ordinary, filtered, and declined rebroadcast attempts. ChangesRouting packet lifecycle
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/mesh/NextHopRouter.cpp (1)
406-438: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRelease directed retransmission copies on
ERRNO_SHOULD_RELEASE.While the broadcast branch (lines 435-438) was correctly updated to release the copied packet if
send()returnsERRNO_SHOULD_RELEASE, the directed retransmission branches immediately above it were missed. This will cause a packet pool memory leak if a directed packet's transmission is declined or fails synchronously (e.g., if the interface is disabled).Apply the same conditional release logic to the directed branches.
🐛 Proposed fix for the missing release checks
- if (auto *copy = packetPool.allocCopy(*p.packet)) - FloodingRouter::send(copy); + if (auto *copy = packetPool.allocCopy(*p.packet)) { + if (FloodingRouter::send(copy) == ERRNO_SHOULD_RELEASE) + packetPool.release(copy); + } } else { `#if` NEXTHOP_EARLY_FLOOD_ON_UNVERIFIED // M4 (gated): if the route isn't proven healthy, don't spend a second directed // attempt - start flooding one retry sooner to cut recovery latency. A verified // route (fresh, zero recent failures) keeps the unchanged directed-retry path so // the sparse-mesh happy path is untouched. RouteHealth *h = findRouteHealth(p.packet->to); bool verified = h && h->consecutiveFailures == 0 && !isRouteStale(*h, now); if (!verified) { p.packet->next_hop = NO_NEXT_HOP_PREFERENCE; meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to); if (sentTo) sentTo->next_hop = NO_NEXT_HOP_PREFERENCE; - if (auto *copy = packetPool.allocCopy(*p.packet)) - FloodingRouter::send(copy); + if (auto *copy = packetPool.allocCopy(*p.packet)) { + if (FloodingRouter::send(copy) == ERRNO_SHOULD_RELEASE) + packetPool.release(copy); + } } else { - if (auto *copy = packetPool.allocCopy(*p.packet)) - NextHopRouter::send(copy); + if (auto *copy = packetPool.allocCopy(*p.packet)) { + if (NextHopRouter::send(copy) == ERRNO_SHOULD_RELEASE) + packetPool.release(copy); + } } `#else` - if (auto *copy = packetPool.allocCopy(*p.packet)) - NextHopRouter::send(copy); + if (auto *copy = packetPool.allocCopy(*p.packet)) { + if (NextHopRouter::send(copy) == ERRNO_SHOULD_RELEASE) + packetPool.release(copy); + } `#endif` } } else { // Note: we call the superclass version because we don't want to have our version of send() add a new // retransmission record if (auto *copy = packetPool.allocCopy(*p.packet)) { if (FloodingRouter::send(copy) == ERRNO_SHOULD_RELEASE) packetPool.release(copy); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/mesh/NextHopRouter.cpp` around lines 406 - 438, Update the directed retransmission branches in NextHopRouter’s send flow, including both the early-flood and verified-route paths, to check FloodingRouter::send or NextHopRouter::send for ERRNO_SHOULD_RELEASE and release the copied packet through packetPool when returned. Preserve the existing routing decisions and copy allocation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/mesh/NextHopRouter.cpp`:
- Around line 406-438: Update the directed retransmission branches in
NextHopRouter’s send flow, including both the early-flood and verified-route
paths, to check FloodingRouter::send or NextHopRouter::send for
ERRNO_SHOULD_RELEASE and release the copied packet through packetPool when
returned. Preserve the existing routing decisions and copy allocation behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 36723a80-aedf-4226-a929-59ef8a260683
📒 Files selected for processing (5)
src/mesh/NextHopRouter.cppsrc/mesh/NextHopRouter.hsrc/modules/RoutingModule.cppsrc/mqtt/MQTT.cpptest/test_nexthop_routing/test_main.cpp
⚡ Try this PR in the Web FlasherNote Building this pull request… the flash button, badges and supported-board |
relayOpaquePacket() allocates a copy and returns Router::send(relay) == ERRNO_OK, discarding ERRNO_SHOULD_RELEASE. The interface returns that for NODENUM_BROADCAST_NO_LORA, so the copy is never freed and one pool slot leaks per frame. The opaque path is reached for packets on a channel we have no key for, so no key or PSK is needed: a frame with an unknown channel hash, to=NODENUM_BROADCAST_NO_LORA, a nonzero id and hop_limit>0 leaks a slot, and roughly MAX_PACKETS of them exhaust the pool until reboot. #11087 fixed this pattern in perhapsRebroadcast and the retransmission paths but did not cover relayOpaquePacket, which was added separately with the opaque relay path.
RadioLibInterface::send()returnsERRNO_SHOULD_RELEASEwithout releasing whentoisNODENUM_BROADCAST_NO_LORA. OnlyMeshService::sendToMeshhonoured that return; other callers discarded it and leaked a packet pool slot each time.isBroadcast()acceptsNODENUM_BROADCAST_NO_LORAand nothing validates thetofield of an inbound packet, so a received frame addressed to it reached the rebroadcast path and the ack path.Changes:
NextHopRouter::perhapsRebroadcastreturns early forNODENUM_BROADCAST_NO_LORA.NextHopRouter::perhapsRebroadcast,NextHopRouter::doRetransmissions,RoutingModule::sendAckNakand the MQTT ack path release onERRNO_SHOULD_RELEASE.Tests in
test_nexthop_routingcover the guard, a normal broadcast control, and the release path.Summary by CodeRabbit
Bug Fixes
Tests