Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports (#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 #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.
This commit is contained in:
Ben Meadors
2026-07-06 13:18:25 -05:00
committed by GitHub
co-authored by GitHub
parent 1e66ecff60
commit ed03a69555
10 changed files with 80 additions and 24 deletions
+45
View File
@@ -257,6 +257,50 @@ void test_ws_v1_migration_discardsLastHeard()
b.saveIfDirty();
}
// Shrink safety: a warm.dat snapshot recording more entries than this build's
// WARM_NODE_COUNT (e.g. written before a per-platform tier reduction) must be
// rejected cleanly at the header check - load() starts empty instead of reading
// past entries[]. (The nRF52840 raw-flash ring backend has no such cliff: it
// replays records through place(), whose LRU admission keeps the newest.)
void test_ws_load_rejectsOversizedSnapshot()
{
WarmNodeStore a;
a.absorb(0xA00, 111, NULL);
if (!a.saveIfDirty()) {
TEST_IGNORE_MESSAGE("Filesystem not available in this test environment");
return;
}
// Patch the header's count field (offset 8) to one past capacity.
std::vector<uint8_t> buf;
{
auto f = FSCom.open("/prefs/warm.dat", FILE_O_READ);
if (!f) {
TEST_IGNORE_MESSAGE("warm.dat not readable in this environment");
return;
}
buf.resize(f.size());
f.read(buf.data(), buf.size());
f.close();
}
TEST_ASSERT_TRUE(buf.size() >= 16);
const uint16_t oversized = (uint16_t)(WARM_NODE_COUNT + 1);
memcpy(buf.data() + 8, &oversized, sizeof(oversized));
{
auto f = FSCom.open("/prefs/warm.dat", FILE_O_WRITE);
TEST_ASSERT_TRUE((bool)f);
f.write(buf.data(), buf.size());
f.close();
}
WarmNodeStore b;
b.load();
TEST_ASSERT_EQUAL(0, b.count()); // rejected as invalid, started empty
b.clear();
b.saveIfDirty();
}
WS_TEST_ENTRY void setup()
{
initializeTestEnvironment();
@@ -273,6 +317,7 @@ WS_TEST_ENTRY void setup()
RUN_TEST(test_ws_remove_and_clear);
RUN_TEST(test_ws_persistence_roundTrip);
RUN_TEST(test_ws_v1_migration_discardsLastHeard);
RUN_TEST(test_ws_load_rejectsOversizedSnapshot);
exit(UNITY_END());
}