Skip to content

esp32: Close the NVS handle when nvs_get_binary returns early - #2451

Merged
bettio merged 1 commit into
atomvm:release-0.7from
iobee-io:nvs-close
Oct 29, 2026
Merged

bettio merged 1 commit into
atomvm:release-0.7from
iobee-io:nvs-close

Conversation

@gabrielmancini

Copy link
Copy Markdown

nif_esp_nvs_fetch_binary() (esp:nvs_get_binary/1,2) opens an NVS handle and closes it only after reading the blob. Three returns after a successful nvs_open skip nvs_close:

  • the key does not exist ({error, not_found}, which esp:nvs_get_binary turns into undefined)
  • nvs_get_blob fails while reading the size
  • memory_ensure_free fails (the not_found tuple or the binary)

Each of them loses the handle ESP-IDF allocated in nvs_open, about 72 bytes of heap. Reading a missing key now and then is common, for example an optional setting, and it went unnoticed. Reading one on every loop does not: we read an optional setting on every 2 s poll, and an ESP32 without PSRAM lost ~1.2 KB/min and aborted with Cannot handle out of memory every ~20 minutes.

The fix calls nvs_close before each of those returns. put_binary and both erase NIFs already close before returning.

Test. test_nvs (ESP32) writes a key so the namespace exists, then reads a missing key 500 times as a warm-up and 500 times more, and checks the free heap (esp32_free_heap_size) did not shrink by 4 KB or more across the second batch.

  • QEMU esp32, without the fix: {leaked,36032}, 36 KB lost in 500 reads; with it, test_nvs passes (test_socket fails here as it does on plain release-0.7, no network inside QEMU).
  • On hardware: the same board ran 74 minutes with a flat free heap (47–49 KB) once our firmware stopped reading the key on every poll. The VM fix itself is proven in QEMU only so far.

These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

nif_esp_nvs_fetch_binary() opens an NVS handle and closed it only after
reading the blob. A missing key, an error reading its size or no memory
for the result returned without nvs_close(), so each such read lost the
handle ESP-IDF allocated: about 72 bytes on the heap. Reading a missing
key in a loop slowly ran a device out of memory.

test_nvs reads a missing key 500 times and checks the free heap does not
shrink; without this change it loses 36 KB.

Signed-off-by: Gabriel Mancini <gabriel.mancini@gmail.com>
@gabrielmancini

Copy link
Copy Markdown
Author

Rebased on release-0.7 after #2436 was merged: the conflict was the CHANGELOG and the test list only, the code is the approved one.

@bettio
bettio merged commit d749c4c into atomvm:release-0.7 Oct 29, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants