Skip to content

Router: defer nested local sends to avoid handleReceived re-entrancy - #11191

Merged
thebentern merged 1 commit into
developfrom
router-nested-send-depth-guard
Jul 25, 2026
Merged

Router: defer nested local sends to avoid handleReceived re-entrancy#11191
thebentern merged 1 commit into
developfrom
router-nested-send-depth-guard

Conversation

@caveman99

@caveman99 caveman99 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Guards Router::handleReceived with a depth counter so a module sending from inside callModules defers its loopback instead of re-entering and overflowing the nRF52 task stack on a config save. Replaces draft #11155.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of locally addressed packets and broadcasts.
    • Prevented recursive packet processing when modules send packets during message handling.
    • Ensured deferred packets are processed reliably without duplicate local deliveries or crashes.
    • Added graceful handling when the deferred delivery queue reaches capacity.
  • Tests

    • Added coverage for nested sends, chained replies, broadcasts, and queue overflow scenarios.

Prevents an nRF52 task-stack overflow on config save. Replaces draft #11155.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Router local loopback now routes through depth-aware delivery. Nested packets are copied into a bounded deferred queue, drained by the outermost receive handler, and dispatched without recursive re-entry. Tests cover fanout, chained replies, queue overflow, and phone delivery.

Changes

Deferred local delivery

Layer / File(s) Summary
Receive dispatch boundary
src/mesh/Router.h, src/mesh/Router.cpp
Router separates dispatchReceived() from handleReceived() and gives encrypted packet copies per dispatch.
Local delivery deferral and drain
src/mesh/Router.cpp, src/mesh/Router.h
Local and broadcast loopbacks use deliverLocal(), bounded queue helpers, receive-depth tracking, and outermost-frame draining.
Deferred delivery validation
test/test_mesh_module/test_main.cpp
New test modules and Unity cases validate non-reentrant fanout, breadth-first chains, overflow drops, delivery counts, and registration.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MeshModule
  participant Router
  participant DeferredQueue
  participant PhoneQueue

  MeshModule->>Router: sendLocal(packet, source)
  Router->>Router: deliverLocal(packet, source)
  Router->>DeferredQueue: enqueue nested packet
  Router->>Router: outer handleReceived drains queue
  Router->>Router: dispatchReceived(packet, source)
  Router->>PhoneQueue: deliver self-addressed reply
Loading

Possibly related PRs

Suggested labels: bugfix, mesh

Suggested reviewers: thebentern, jp-bennett

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning It summarizes the fix but omits the required attestations, testing checklist, and regression/device details from the template. Replace the placeholder template text and add the attestations section, testing confirmation, and any device/regression notes required by the repository.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the Router re-entrancy fix for nested local sends.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch router-nested-send-depth-guard

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.

@caveman99
caveman99 requested review from RCGV1 and thebentern July 24, 2026 13:21
@caveman99

Copy link
Copy Markdown
Member Author

A different approach to #11155 (credits to @Ixitxachitl for the initial analysis) and complementing the #11190 stack fix, this prevents the original recursive callModules polluting the stack in the first place.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/test_mesh_module/test_main.cpp (1)

690-712: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded capacity+2 requires manual sync with Router.h's private constant.

burst = 6 is deferredLocalCapacity (4) + 2, kept in sync only by a comment. If the capacity in Router.h changes, this test silently stops testing the actual overflow boundary (or the drop-count assertion at Line 701 breaks) unless someone remembers the comment. Consider exposing deferredLocalCapacity under the existing #ifdef PIO_UNIT_TESTING public: block in Router.h (same pattern as deferredLocalPending()) so the test can reference it directly.

♻️ Proposed fix
-    const uint32_t burst = 6; // deferredLocalCapacity (4) + 2 - keep in sync with Router.h
+    const uint32_t burst = mockRouter->deferredLocalCapacityForTest() + 2;
// src/mesh/Router.h, inside the existing `#ifdef` PIO_UNIT_TESTING public: block
uint8_t deferredLocalCapacityForTest() const { return deferredLocalCapacity; }
🤖 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 `@test/test_mesh_module/test_main.cpp` around lines 690 - 712, Expose Router’s
private deferredLocalCapacity through a deferredLocalCapacityForTest() accessor
in the existing PIO_UNIT_TESTING public block, following the
deferredLocalPending() pattern. Update
test_deferredQueueOverflow_dropsGracefully() to derive burst from that accessor
plus the intended overflow amount, removing the hardcoded capacity and
synchronization comment while preserving the drop-count assertions.
src/mesh/Router.cpp (1)

1256-1292: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comments for the new deferred-delivery mechanism exceed the repo's 1-2 line comment cap in both files. Same root cause: the stack-overflow-avoidance rationale was written as multi-paragraph blocks instead of terse comments.

  • src/mesh/Router.cpp#L1256-L1292: trim the 6-line drain-rationale comment (Lines 1266-1271) and the 6-line ownership comment in dispatchReceived() (Lines 1287-1292) to 1-2 lines each.
  • src/mesh/Router.h#L163-L205: trim the dispatchReceived() doc (Lines 163-167, 4 lines), deliverLocal() doc (Lines 170-177, 7 lines), and ring-buffer doc (Lines 192-195, 4 lines) to 1-2 lines each.

As per coding guidelines, "Keep code comments minimal—one or two lines maximum—and comment only when the reason is not obvious; do not restate straightforward code or add multi-paragraph explanatory blocks."

🤖 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/Router.cpp` around lines 1256 - 1292, Trim the deferred-delivery
comments to the repository’s one- or two-line limit: in src/mesh/Router.cpp
lines 1256-1292, shorten the handleReceived() drain rationale and
dispatchReceived() ownership rationale; in src/mesh/Router.h lines 163-205,
shorten the dispatchReceived() documentation, deliverLocal() documentation, and
deferred ring-buffer documentation. Preserve only the essential non-obvious
rationale and remove redundant implementation detail.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/mesh/Router.cpp`:
- Around line 1256-1292: Trim the deferred-delivery comments to the repository’s
one- or two-line limit: in src/mesh/Router.cpp lines 1256-1292, shorten the
handleReceived() drain rationale and dispatchReceived() ownership rationale; in
src/mesh/Router.h lines 163-205, shorten the dispatchReceived() documentation,
deliverLocal() documentation, and deferred ring-buffer documentation. Preserve
only the essential non-obvious rationale and remove redundant implementation
detail.

In `@test/test_mesh_module/test_main.cpp`:
- Around line 690-712: Expose Router’s private deferredLocalCapacity through a
deferredLocalCapacityForTest() accessor in the existing PIO_UNIT_TESTING public
block, following the deferredLocalPending() pattern. Update
test_deferredQueueOverflow_dropsGracefully() to derive burst from that accessor
plus the intended overflow amount, removing the hardcoded capacity and
synchronization comment while preserving the drop-count assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 10e02cba-7e08-45bf-9529-8e0c570d93b1

📥 Commits

Reviewing files that changed from the base of the PR and between 7754068 and 4065188.

📒 Files selected for processing (3)
  • src/mesh/Router.cpp
  • src/mesh/Router.h
  • test/test_mesh_module/test_main.cpp

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (31)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Meshnology W12 meshnology_w12 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Pocket V3 rak_wismesh_pocket nrf52840
RAK WisMesh Pod rak_wismesh_pod nrf52840
RAK WisMesh Repeater Mini V2 rak_wismesh_repeater_mini nrf52840
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-23. Updated for 4065188.

@caveman99 caveman99 added the bugfix Pull request that fixes bugs label Jul 24, 2026
@thebentern
thebentern added this pull request to the merge queue Jul 24, 2026
Merged via the queue into develop with commit b46c8c9 Jul 25, 2026
102 of 104 checks passed
madeofstown pushed a commit to madeofstown/meshtastic-firmware that referenced this pull request Jul 26, 2026
…eshtastic#11191)

Prevents an nRF52 task-stack overflow on config save. Replaces draft meshtastic#11155.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants