diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp index 41f8e879da3..7c7f239c20e 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp @@ -899,6 +899,27 @@ TEST_CASE("deinit partition doesn't affect other partition's open handles", "[nv TEST_ESP_OK(nvs_flash_deinit_partition(TEST_SECONDARY_PARTITION_NAME)); } +TEST_CASE("deinit with open handles frees them", "[nvs]") +{ + // nvs_flash_deinit_partition must delete leftover open + // handles (same as nvs_close), not only erase them from the C API handle list. + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + + nvs_handle_t handle_1; + nvs_handle_t handle_2; + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "ns1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "ns2", NVS_READWRITE, &handle_2)); + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 2); + + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + // Stale handle ids must be harmless after deinit already freed the entries. + nvs_close(handle_1); + nvs_close(handle_2); +} + TEST_CASE("nvs iterator nvs_entry_find invalid parameter test", "[nvs]") { // TC verifies that nvs_entry_find returns ESP_ERR_INVALID_ARG on invalid parameters. diff --git a/components/nvs_flash/include/nvs_flash.h b/components/nvs_flash/include/nvs_flash.h index e0828f0a362..5dba3797a4d 100644 --- a/components/nvs_flash/include/nvs_flash.h +++ b/components/nvs_flash/include/nvs_flash.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -139,6 +139,10 @@ esp_err_t nvs_flash_init_partition_bdl(const char* partition_label, esp_blockdev * * Default NVS partition is the partition with "nvs" label in the partition table. * + * @note Prefer closing all open handles with nvs_close() before deinitializing. + * Any handles still open for this partition are closed and freed here; + * using them afterwards is invalid (nvs_close() on such a handle is a no-op). + * * @return * - ESP_OK on success (storage was deinitialized) * - ESP_ERR_NVS_NOT_INITIALIZED if the storage was not initialized prior to this call @@ -150,6 +154,10 @@ esp_err_t nvs_flash_deinit(void); * * @param[in] partition_label Label of the partition * + * @note Prefer closing all open handles with nvs_close() before deinitializing. + * Any handles still open for this partition are closed and freed here; + * using them afterwards is invalid (nvs_close() on such a handle is a no-op). + * * @return * - ESP_OK on success * - ESP_ERR_NVS_NOT_INITIALIZED if the storage for given partition was not diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index 212f32572d2..87cb9fad72b 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -85,7 +85,9 @@ static esp_err_t close_handles_and_deinit(const char* part_name) auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), belongs_to_part); + // Same as nvs_close(): erase from the list and delete the entry (frees NVSHandleSimple). while (it != end(s_nvs_handles)) { + delete static_cast(it); s_nvs_handles.erase(it); it = find_if(begin(s_nvs_handles), end(s_nvs_handles), belongs_to_part); }