Skip to content

Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports - #10898

Merged
thebentern merged 3 commits into
developfrom
fix/nrf52-heap-rightsizing
Jul 6, 2026
Merged

Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports#10898
thebentern merged 3 commits into
developfrom
fix/nrf52-heap-rightsizing

Conversation

@thebentern

@thebentern thebentern commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

Field reports on 2.8.0 (see Discord) show nRF52840 devices at 99% heap — 114/115 KB — within minutes of boot with a client connected. On nRF52 operator new asserts on OOM, so an affected device is one allocation away from a reboot loop.
The actual growth came from the 2.8.0 cache tier ladders, which gave nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only part has a roomy heap. The nRF52840 arena is ~125 KB (256 KB minus 24 KB SoftDevice minus ~110 KB static, and shared with the FreeRTOS task stacks), and boot-time caches were consuming ~30 KB of it.

Changes (all one-line tier constants; no formats, no features removed)

Change Before → After Saves (nRF52840)
TMM unified cache (mesh-pb-constants.h) 1000 → 250 entries; unclassified fallthrough → 400 7.5 KB
Warm node store 200 → 100; non-XXAA fallthrough 320 → 100 4.0 KB (+12.8 KB latent)
MESSAGE_HISTORY_LIMIT 20 → 10 (tier classic ESP32 already ships) 2.2 KB
MAX_RX_TOPHONE 32 → 16 → static packet pool 70 → 54 slots ~6.6 KB .bss → heap
PacketHistory hash index (nrf52.ini) off arch-wide; O(n) over 240 records is µs at LoRa rates 1.0 KB
OLEDDISPLAY_REDUCE_MEMORY (nrf52.ini) drops ThingPulse back buffer; the 5 TFT variants -U it (TFTDisplay.cpp needs buffer_back) 1.0 KB (OLED)
T1000-E stale "for testing" TMM override 1024 → tier default 10.2 KB on that variant

ESP32 targets are untouched (already right-sized by #10741). The TMM fallthrough change also lowers RP2040/RP2350 from 1000 → 400, matching the classic-ESP32 tier.

Measured result (rak4631, arm-none-eabi-size -A)

  • Heap arena (.heap section): 124,572 → 131,180 B (+6,608 B from the static packet pool cut)
  • Boot-time heap allocations: −15.7 KB (TMM −7.5K, warm −4K, message pool −2.2K, hash −1K, OLED −1K)
  • Net on the field-report device class: ~+22 KB free heap (from ~1 KB free to ~13 KB static + runtime headroom)

Migration

  • nRF52840 warm flash ring: load() replays through place() (LRU admission), so the newest 100 identities survive the shrink; ring format unchanged.
  • File-backend (/prefs/warm.dat) targets whose count shrinks reject the oversized snapshot cleanly and start empty (it's a cache; keys re-learn). New test test_ws_load_rejectsOversizedSnapshot covers this.
  • MessageStore::loadFromFlash() clamps to the new limit (cosmetic first-boot ordering quirk, self-heals).

Trade-offs

TMM tracks rate/dedup state for 250 concurrently-active senders (>2× the 120-node hot store) instead of 1000; warm DM-key retention covers 120 hot + 100 warm identities; on-device message log depth halves (phone apps keep their own history). All values match tiers already field-proven on classic ESP32.

If field telemetry still shows <15% free at steady state, the pre-agreed fallback knobs are: TMM 250→128, WARM 100→64, MESSAGE 10→8, and an nRF52 MAX_SATELLITE_NODES 40→24 branch.

Verification

  • Native suites: Docker 536/536 passed; native-macos warm-store suite 13/13 (incl. the new shrink-safety test)
  • Builds green: rak4631 (OLED), heltec-mesh-node-t114 (TFT, exercises the -U path), tracker-t1000-e (variant override removal)
  • Follow-ups in flight: central memory-class header so future features can't fall through to the largest tier, per-subsystem heap accounting in the boot log, SoftDevice RAM-reservation right-sizing, and CI RAM budgets.

Summary by CodeRabbit

Summary

  • Bug Fixes

    • Improved memory handling on nRF52840 builds by lowering message history and tuning mesh cache sizing/counters, with extra adjustments for NRF52840_XXAA.
    • Reduced display memory usage on multiple nRF52840 variants by configuring the TFT/OLED dirty-window diffing mode.
    • Disabled traffic management defaults for the tracker-t1000-e variant.
  • Tests

    • Added coverage ensuring oversized warm-node snapshots are rejected and won’t load invalid data.

Field reports on 2.8.0 show nRF52840 devices at 99% heap (114/115 KB)
within minutes of boot; operator new asserts on OOM, so these devices
are one allocation from a reboot. The 2.8.0 cache sizing ladders gave
nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only
part has a roomy heap - the arena is actually ~125 KB shared with the
FreeRTOS task stacks.

Per-target retiers (nRF52840 unless noted):
- Traffic Management cache 1000 -> 250 entries (10 KB -> 2.5 KB); the
  unclassified fallthrough drops 1000 -> 400 to match the classic-ESP32
  tier (also affects RP2040/RP2350)
- Warm node store 200 -> 100 entries (8 KB -> 4 KB); the non-XXAA
  fallthrough drops 320 -> 100 so an unclassified RAM-constrained part
  can't boot-allocate 12.8 KB
- MESSAGE_HISTORY_LIMIT 20 -> 10 (text pool 4.4 KB -> 2.2 KB), the tier
  classic ESP32 already ships
- MAX_RX_TOPHONE 32 -> 16, shrinking the static packet pool 70 -> 54
  slots (~6.6 KB of .bss returned to the heap arena)
- PacketHistory hash index off arch-wide (1 KB); O(n) over 240 records
  is negligible at LoRa packet rates
- OLEDDISPLAY_REDUCE_MEMORY arch-wide (~1 KB OLED back buffer); the five
  TFT variants -U it because TFTDisplay.cpp needs buffer_back for
  dirty-window diffing
- Drop the stale "for testing" 1024-entry TMM override on T1000-E

Measured on rak4631: heap arena grows 124,572 -> 131,180 B and boot
allocations drop ~15.7 KB, roughly +22 KB free heap on the field-report
device class.

Migration: the nRF52840 warm flash ring replays through place() (LRU),
so the newest 100 identities survive the shrink; the file backend
rejects oversized snapshots cleanly (new test covers this). Native
suites pass (536/536 Docker, 13/13 native-macos warm store); rak4631,
heltec-mesh-node-t114 (TFT) and tracker-t1000-e build green.
@github-actions

github-actions Bot commented Jul 6, 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 (26)
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
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
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-05. Updated for 65fc9ce.

@github-actions github-actions Bot added needs-review Needs human review enhancement New feature or request labels Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b0f57a8d-0884-44b9-b1d0-7ac4ce0cf439

📥 Commits

Reviewing files that changed from the base of the PR and between 2dea484 and 65fc9ce.

📒 Files selected for processing (2)
  • variants/nrf52840/heltec_mesh_node_t114/platformio.ini
  • variants/nrf52840/heltec_mesh_solar/platformio.ini
💤 Files with no reviewable changes (2)
  • variants/nrf52840/heltec_mesh_solar/platformio.ini
  • variants/nrf52840/heltec_mesh_node_t114/platformio.ini

📝 Walkthrough

Walkthrough

This PR lowers memory-related limits for NRF52840_XXAA and related constrained builds, updates variant build flags for OLED and PacketHistory behavior, removes forced traffic management defaults from tracker-t1000-e, and adds a warm-store snapshot rejection test.

Changes

NRF52840 Memory Reduction

Layer / File(s) Summary
Constant/preprocessor limit adjustments
src/MessageStore.h, src/mesh/mesh-pb-constants.h
Expands the reduced MESSAGE_HISTORY_LIMIT branch for NRF52840_XXAA and adjusts MAX_RX_TOPHONE, WARM_NODE_COUNT, and TRAFFIC_MANAGEMENT_CACHE_SIZE for constrained targets.
Warm store oversized snapshot test
test/test_warm_store/test_main.cpp
Adds test_ws_load_rejectsOversizedSnapshot() and registers it in the warm-node test runner.
Build flag updates for variants
variants/nrf52840/heltec_mesh_node_t096/platformio.ini, variants/nrf52840/heltec_mesh_node_t1/platformio.ini, variants/nrf52840/heltec_mesh_node_t114/platformio.ini, variants/nrf52840/heltec_mesh_solar/platformio.ini, variants/nrf52840/rak_wismeshtap/platformio.ini, variants/nrf52840/nrf52.ini
Updates OLED-related build flags across several variants and adds MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH plus OLEDDISPLAY_REDUCE_MEMORY in nrf52.ini.
Remove forced traffic management on tracker-t1000-e
variants/nrf52840/tracker-t1000-e/variant.h
Removes the HAS_TRAFFIC_MANAGEMENT and TRAFFIC_MANAGEMENT_CACHE_SIZE definitions from the variant header.

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

Poem

I nibbled the caches, I trimmed the byte pie,
Then patched a warm store that tried to fly high. 🐇
The build flags now hop in a memory-wise way,
And oversize snapshots get bounced straight away.
Tiny hops, fewer bytes, firmware brighter today.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: reducing nRF52 heap tiers after field reports of heap exhaustion.
Description check ✅ Passed The description covers the problem, concrete tier changes, migration behavior, trade-offs, and verification results.
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.

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

@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 (1)
test/test_warm_store/test_main.cpp (1)

274-288: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use offsetof(WarmStoreHeader, count) here
The header layout is part of the persistence format, but hardcoding 8 makes this patch depend on field order. Using the named offset keeps the test aligned with WarmStoreHeader if the struct changes.

🤖 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_warm_store/test_main.cpp` around lines 274 - 288, The test is
hardcoding the warm store header count offset, which ties the persistence check
to a fragile field order. Update the buffer patch in the warm store test to use
the named offset from WarmStoreHeader via offsetof(WarmStoreHeader, count)
instead of the literal 8. Keep the rest of the oversized-count setup in the same
test helper flow so the patch remains aligned with WarmStoreHeader if the struct
layout changes.
🤖 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 `@test/test_warm_store/test_main.cpp`:
- Around line 274-288: The test is hardcoding the warm store header count
offset, which ties the persistence check to a fragile field order. Update the
buffer patch in the warm store test to use the named offset from WarmStoreHeader
via offsetof(WarmStoreHeader, count) instead of the literal 8. Keep the rest of
the oversized-count setup in the same test helper flow so the patch remains
aligned with WarmStoreHeader if the struct layout changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84916af6-2b10-4753-9776-1618eccab455

📥 Commits

Reviewing files that changed from the base of the PR and between 93250d8 and ce57701.

📒 Files selected for processing (10)
  • src/MessageStore.h
  • src/mesh/mesh-pb-constants.h
  • test/test_warm_store/test_main.cpp
  • variants/nrf52840/heltec_mesh_node_t096/platformio.ini
  • variants/nrf52840/heltec_mesh_node_t1/platformio.ini
  • variants/nrf52840/heltec_mesh_node_t114/platformio.ini
  • variants/nrf52840/heltec_mesh_solar/platformio.ini
  • variants/nrf52840/nrf52.ini
  • variants/nrf52840/rak_wismeshtap/platformio.ini
  • variants/nrf52840/tracker-t1000-e/variant.h
💤 Files with no reviewable changes (1)
  • variants/nrf52840/tracker-t1000-e/variant.h

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Firmware Size Report

22 targets | vs develop: 21 increased, 1 decreased, net +387,668 (+378.6 KB)

Target Size vs develop
picow 1,271,976 📈 +27,096 (+26.5 KB)
pico2w 1,246,644 📈 +26,128 (+25.5 KB)
rak11310 831,240 📈 +25,816 (+25.2 KB)
pico 808,488 📈 +25,800 (+25.2 KB)
seeed_xiao_rp2040 806,704 📈 +25,800 (+25.2 KB)
Show 17 more target(s)
Target Size vs develop
pico2 794,760 📈 +24,984 (+24.4 KB)
seeed_xiao_rp2350 792,904 📈 +24,968 (+24.4 KB)
t-deck-tft 3,832,064 📈 +21,392 (+20.9 KB)
heltec-vision-master-e213-inkhud 2,247,392 📈 +20,032 (+19.6 KB)
elecrow-adv-35-tft 3,435,520 📈 +18,896 (+18.5 KB)
seeed-xiao-s3 2,292,416 📈 +16,272 (+15.9 KB)
heltec-ht62-esp32c3-sx1262 2,150,576 📈 +15,584 (+15.2 KB)
heltec-v3 2,279,632 📈 +15,552 (+15.2 KB)
rak3312 2,287,600 📈 +15,360 (+15.0 KB)
rak11200 1,875,728 📈 +15,120 (+14.8 KB)
station-g3 2,281,088 📈 +14,736 (+14.4 KB)
station-g2 2,281,072 📈 +14,720 (+14.4 KB)
t-eth-elite 2,506,064 📈 +14,576 (+14.2 KB)
tlora-c6 2,382,272 📈 +13,968 (+13.6 KB)
heltec-v4 2,291,152 📈 +13,856 (+13.5 KB)
rak3172 182,492 📉 -3,804 (-3.7 KB)
wio-e5 239,444 📈 +816

Updated for 47a3104

Only USE_TFTDISPLAY variants (t1, t096, wismeshtap) compile
TFTDisplay.cpp and need the lib's buffer_back; t114 and mesh-solar-tft
render through the meshtastic-st7789 driver, which handles the
reduced-memory configuration fine - as #10894 (merged from develop)
already established by defining the flag there. Remove the -U guard and
the per-variant -D (redundant with the arch-wide define in nrf52_base
on this branch). Both variants verified building.
@thebentern

Copy link
Copy Markdown
Contributor Author

On-hardware validation (rak4631, S140 v6, 120-node NodeDB at the hot-store cap, serial client attached — the field-report scenario). All numbers are LocalStats heapFreeBytes/heapTotalBytes at uptime 81 s, reproducible across boots. The integration build = this PR + #10901 + #10900 + #10903 + #10902 merged.

Build Heap total Heap free
develop @ 93250d8 124,572 23,580 (18.9% free)
integration (BT on) 139,236 53,564 (38.5% free) — ×3 boots
integration (BT off, #10902) 139,236 63,124 (45.3% free) — ×2 boots

Free heap more than doubles in the exact scenario from the field reports. The boot log's new MemAudit line (#10900) confirms each retier live: msgstore=2200 warm=4000 nodedb=11040 satmaps=17864 pkthist=4800 tmm=2500 total=42404 (develop equivalents: msgstore 4400, warm 8000, pkthist 5824+1024 hash, tmm 10000). Warm flash-ring migration behaved (WarmStore: ring empty, starting fresh; TMM warm-start preloaded 48 next-hop hints into the 250-slot cache).

Side-note for future work: satmaps=17864 — the four satellite maps are the largest remaining single consumer at the 40-node cap, roughly double the earlier estimate.

@thebentern
thebentern merged commit ed03a69 into develop Jul 6, 2026
93 of 94 checks passed
@thebentern
thebentern deleted the fix/nrf52-heap-rightsizing branch July 6, 2026 18:18
thebentern added a commit that referenced this pull request Jul 6, 2026
…ssert

- MemClass.h's class table claimed RP2350 was MEDIUM while the mapping
  ladder classifies it SMALL (with RP2040) - the table now matches the
  ladder, with a note that RP2350 is a MEDIUM candidate whenever someone
  wants to tune it up (kept SMALL here so this header stays a behavioral
  no-op).
- The boot-cache budget comment referenced a static_assert pinning
  PacketHistory::PacketRecord at 20 B that did not exist (only a layout
  comment). Add the real static_assert so the budget math in
  mesh-pb-constants.h fails to compile if the record layout changes.

Also merges develop (the base #10898 landed there as a squash, which is
what made this stacked branch conflict); develop's mesh-pb-constants.h
is byte-identical to this branch's base, so the resolution keeps the
MemClass ladder unchanged. rak4631 and wio-e5 build green;
test_packet_history 47/47.
thebentern added a commit that referenced this pull request Jul 6, 2026
…aults (#10901)

* Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports

Field reports on 2.8.0 show nRF52840 devices at 99% heap (114/115 KB)
within minutes of boot; operator new asserts on OOM, so these devices
are one allocation from a reboot. The 2.8.0 cache sizing ladders gave
nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only
part has a roomy heap - the arena is actually ~125 KB shared with the
FreeRTOS task stacks.

Per-target retiers (nRF52840 unless noted):
- Traffic Management cache 1000 -> 250 entries (10 KB -> 2.5 KB); the
  unclassified fallthrough drops 1000 -> 400 to match the classic-ESP32
  tier (also affects RP2040/RP2350)
- Warm node store 200 -> 100 entries (8 KB -> 4 KB); the non-XXAA
  fallthrough drops 320 -> 100 so an unclassified RAM-constrained part
  can't boot-allocate 12.8 KB
- MESSAGE_HISTORY_LIMIT 20 -> 10 (text pool 4.4 KB -> 2.2 KB), the tier
  classic ESP32 already ships
- MAX_RX_TOPHONE 32 -> 16, shrinking the static packet pool 70 -> 54
  slots (~6.6 KB of .bss returned to the heap arena)
- PacketHistory hash index off arch-wide (1 KB); O(n) over 240 records
  is negligible at LoRa packet rates
- OLEDDISPLAY_REDUCE_MEMORY arch-wide (~1 KB OLED back buffer); the five
  TFT variants -U it because TFTDisplay.cpp needs buffer_back for
  dirty-window diffing
- Drop the stale "for testing" 1024-entry TMM override on T1000-E

Measured on rak4631: heap arena grows 124,572 -> 131,180 B and boot
allocations drop ~15.7 KB, roughly +22 KB free heap on the field-report
device class.

Migration: the nRF52840 warm flash ring replays through place() (LRU),
so the newest 100 identities survive the shrink; the file backend
rejects oversized snapshots cleanly (new test covers this). Native
suites pass (536/536 Docker, 13/13 native-macos warm store); rak4631,
heltec-mesh-node-t114 (TFT) and tracker-t1000-e build green.

* Add central memory-class ladder (MemClass.h) with fail-safe-small defaults

The 2.8.0 nRF52840 heap exhaustion happened because each RAM-sized cache
picked its per-platform tier from its own chip #ifdef ladder, and every
ladder's fallthrough default was its largest non-PSRAM tier - nRF52 was
never named, so it silently got 1000-entry caches on a ~115 KB arena.

This introduces src/memory/MemClass.h: a single MESHTASTIC_MEM_CLASS
(TINY / SMALL / MEDIUM / LARGE) ranked by usable app heap after platform
overheads, with the deliberate property that an unclassified chip lands
in SMALL - a new target boots with small caches until someone opts it up
in one visible place.

The TMM cache, warm store, MAX_RX_TOPHONE and MAX_SATELLITE_NODES
ladders in mesh-pb-constants.h now key off the class; branches pinned by
something other than RAM stay explicit and say why (nRF52840's SoftDevice
arena, RP2040's warm.dat watchdog bound). MAX_NUM_NODES intentionally
stays separate - it is flash-shaped (nodes.proto vs LittleFS), not
heap-shaped.

A per-class MESHTASTIC_BOOT_CACHE_BUDGET static_assert now covers the
three big boot-allocated caches, so the next cache-adding PR that would
blow a small platform's budget fails to compile instead of exhausting
heap in the field.

No values change for any existing target: rak4631, tbeam, rak11310 and
wio-e5 build byte-identical before/after; all ladders remain
#ifndef-guarded so variant overrides keep working.

* Address review: fix RP2350 class-table doc, add PacketRecord static_assert

- MemClass.h's class table claimed RP2350 was MEDIUM while the mapping
  ladder classifies it SMALL (with RP2040) - the table now matches the
  ladder, with a note that RP2350 is a MEDIUM candidate whenever someone
  wants to tune it up (kept SMALL here so this header stays a behavioral
  no-op).
- The boot-cache budget comment referenced a static_assert pinning
  PacketHistory::PacketRecord at 20 B that did not exist (only a layout
  comment). Add the real static_assert so the budget math in
  mesh-pb-constants.h fails to compile if the record layout changes.

Also merges develop (the base #10898 landed there as a squash, which is
what made this stacked branch conflict); develop's mesh-pb-constants.h
is byte-identical to this branch's base, so the resolution keeps the
MemClass ladder unchanged. rak4631 and wio-e5 build green;
test_packet_history 47/47.

* Address review: share PACKETHISTORY_MAX, trim policy comments

- Hoist PACKETHISTORY_MAX from PacketHistory.cpp into
  mesh-pb-constants.h (next to the MAX_NUM_NODES it derives from) so the
  constructor clamp and the boot-cache budget static_assert use one
  definition instead of hand-mirrored arithmetic that could drift. The
  expression stays valid where MAX_NUM_NODES resolves at runtime
  (ESP32-S3, portduino); the pointless 2.0 double math becomes integer.
- Trim the MemClass.h header (36 -> 16 comment lines) and the budget /
  sizing-policy comments per the repo comment-length guideline, keeping
  the class table, the fail-safe-small rule, the override mechanism, and
  the include-order constraint.

rak4631 (compile-time MAX_NUM_NODES) and heltec-v3 (runtime) build
green; test_packet_history 47/47.
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
…meshtastic#10898)

* Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports

Field reports on 2.8.0 show nRF52840 devices at 99% heap (114/115 KB)
within minutes of boot; operator new asserts on OOM, so these devices
are one allocation from a reboot. The 2.8.0 cache sizing ladders gave
nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only
part has a roomy heap - the arena is actually ~125 KB shared with the
FreeRTOS task stacks.

Per-target retiers (nRF52840 unless noted):
- Traffic Management cache 1000 -> 250 entries (10 KB -> 2.5 KB); the
  unclassified fallthrough drops 1000 -> 400 to match the classic-ESP32
  tier (also affects RP2040/RP2350)
- Warm node store 200 -> 100 entries (8 KB -> 4 KB); the non-XXAA
  fallthrough drops 320 -> 100 so an unclassified RAM-constrained part
  can't boot-allocate 12.8 KB
- MESSAGE_HISTORY_LIMIT 20 -> 10 (text pool 4.4 KB -> 2.2 KB), the tier
  classic ESP32 already ships
- MAX_RX_TOPHONE 32 -> 16, shrinking the static packet pool 70 -> 54
  slots (~6.6 KB of .bss returned to the heap arena)
- PacketHistory hash index off arch-wide (1 KB); O(n) over 240 records
  is negligible at LoRa packet rates
- OLEDDISPLAY_REDUCE_MEMORY arch-wide (~1 KB OLED back buffer); the five
  TFT variants -U it because TFTDisplay.cpp needs buffer_back for
  dirty-window diffing
- Drop the stale "for testing" 1024-entry TMM override on T1000-E

Measured on rak4631: heap arena grows 124,572 -> 131,180 B and boot
allocations drop ~15.7 KB, roughly +22 KB free heap on the field-report
device class.

Migration: the nRF52840 warm flash ring replays through place() (LRU),
so the newest 100 identities survive the shrink; the file backend
rejects oversized snapshots cleanly (new test covers this). Native
suites pass (536/536 Docker, 13/13 native-macos warm store); rak4631,
heltec-mesh-node-t114 (TFT) and tracker-t1000-e build green.

* Drop stale OLEDDISPLAY_REDUCE_MEMORY -U on t114 / mesh-solar-tft

Only USE_TFTDISPLAY variants (t1, t096, wismeshtap) compile
TFTDisplay.cpp and need the lib's buffer_back; t114 and mesh-solar-tft
render through the meshtastic-st7789 driver, which handles the
reduced-memory configuration fine - as meshtastic#10894 (merged from develop)
already established by defining the flag there. Remove the -U guard and
the per-variant -D (redundant with the arch-wide define in nrf52_base
on this branch). Both variants verified building.
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
…aults (meshtastic#10901)

* Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports

Field reports on 2.8.0 show nRF52840 devices at 99% heap (114/115 KB)
within minutes of boot; operator new asserts on OOM, so these devices
are one allocation from a reboot. The 2.8.0 cache sizing ladders gave
nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only
part has a roomy heap - the arena is actually ~125 KB shared with the
FreeRTOS task stacks.

Per-target retiers (nRF52840 unless noted):
- Traffic Management cache 1000 -> 250 entries (10 KB -> 2.5 KB); the
  unclassified fallthrough drops 1000 -> 400 to match the classic-ESP32
  tier (also affects RP2040/RP2350)
- Warm node store 200 -> 100 entries (8 KB -> 4 KB); the non-XXAA
  fallthrough drops 320 -> 100 so an unclassified RAM-constrained part
  can't boot-allocate 12.8 KB
- MESSAGE_HISTORY_LIMIT 20 -> 10 (text pool 4.4 KB -> 2.2 KB), the tier
  classic ESP32 already ships
- MAX_RX_TOPHONE 32 -> 16, shrinking the static packet pool 70 -> 54
  slots (~6.6 KB of .bss returned to the heap arena)
- PacketHistory hash index off arch-wide (1 KB); O(n) over 240 records
  is negligible at LoRa packet rates
- OLEDDISPLAY_REDUCE_MEMORY arch-wide (~1 KB OLED back buffer); the five
  TFT variants -U it because TFTDisplay.cpp needs buffer_back for
  dirty-window diffing
- Drop the stale "for testing" 1024-entry TMM override on T1000-E

Measured on rak4631: heap arena grows 124,572 -> 131,180 B and boot
allocations drop ~15.7 KB, roughly +22 KB free heap on the field-report
device class.

Migration: the nRF52840 warm flash ring replays through place() (LRU),
so the newest 100 identities survive the shrink; the file backend
rejects oversized snapshots cleanly (new test covers this). Native
suites pass (536/536 Docker, 13/13 native-macos warm store); rak4631,
heltec-mesh-node-t114 (TFT) and tracker-t1000-e build green.

* Add central memory-class ladder (MemClass.h) with fail-safe-small defaults

The 2.8.0 nRF52840 heap exhaustion happened because each RAM-sized cache
picked its per-platform tier from its own chip #ifdef ladder, and every
ladder's fallthrough default was its largest non-PSRAM tier - nRF52 was
never named, so it silently got 1000-entry caches on a ~115 KB arena.

This introduces src/memory/MemClass.h: a single MESHTASTIC_MEM_CLASS
(TINY / SMALL / MEDIUM / LARGE) ranked by usable app heap after platform
overheads, with the deliberate property that an unclassified chip lands
in SMALL - a new target boots with small caches until someone opts it up
in one visible place.

The TMM cache, warm store, MAX_RX_TOPHONE and MAX_SATELLITE_NODES
ladders in mesh-pb-constants.h now key off the class; branches pinned by
something other than RAM stay explicit and say why (nRF52840's SoftDevice
arena, RP2040's warm.dat watchdog bound). MAX_NUM_NODES intentionally
stays separate - it is flash-shaped (nodes.proto vs LittleFS), not
heap-shaped.

A per-class MESHTASTIC_BOOT_CACHE_BUDGET static_assert now covers the
three big boot-allocated caches, so the next cache-adding PR that would
blow a small platform's budget fails to compile instead of exhausting
heap in the field.

No values change for any existing target: rak4631, tbeam, rak11310 and
wio-e5 build byte-identical before/after; all ladders remain
#ifndef-guarded so variant overrides keep working.

* Address review: fix RP2350 class-table doc, add PacketRecord static_assert

- MemClass.h's class table claimed RP2350 was MEDIUM while the mapping
  ladder classifies it SMALL (with RP2040) - the table now matches the
  ladder, with a note that RP2350 is a MEDIUM candidate whenever someone
  wants to tune it up (kept SMALL here so this header stays a behavioral
  no-op).
- The boot-cache budget comment referenced a static_assert pinning
  PacketHistory::PacketRecord at 20 B that did not exist (only a layout
  comment). Add the real static_assert so the budget math in
  mesh-pb-constants.h fails to compile if the record layout changes.

Also merges develop (the base meshtastic#10898 landed there as a squash, which is
what made this stacked branch conflict); develop's mesh-pb-constants.h
is byte-identical to this branch's base, so the resolution keeps the
MemClass ladder unchanged. rak4631 and wio-e5 build green;
test_packet_history 47/47.

* Address review: share PACKETHISTORY_MAX, trim policy comments

- Hoist PACKETHISTORY_MAX from PacketHistory.cpp into
  mesh-pb-constants.h (next to the MAX_NUM_NODES it derives from) so the
  constructor clamp and the boot-cache budget static_assert use one
  definition instead of hand-mirrored arithmetic that could drift. The
  expression stays valid where MAX_NUM_NODES resolves at runtime
  (ESP32-S3, portduino); the pointless 2.0 double math becomes integer.
- Trim the MemClass.h header (36 -> 16 comment lines) and the budget /
  sizing-policy comments per the repo comment-length guideline, keeping
  the class table, the fail-safe-small rule, the override mechanism, and
  the include-order constraint.

rak4631 (compile-time MAX_NUM_NODES) and heltec-v3 (runtime) build
green; test_packet_history 47/47.
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 enhancement New feature or request needs-review Needs human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant