Merge branch 'fix/heap-minimum-free-size' into 'master'

fix(heap): Do not consider newly registered heap in minimum free size calculation

Closes IDF-15682

See merge request espressif/esp-idf!48681
This commit is contained in:
Guillaume Souchere
2026-05-29 07:46:52 +02:00
5 changed files with 68 additions and 7 deletions

View File

@@ -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
*/
@@ -345,10 +345,12 @@ esp_err_t heap_caps_monitor_local_minimum_free_size_start(void)
heap = SLIST_FIRST(&registered_heaps);
for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) {
size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap);
if (heap->heap != NULL) {
size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap);
if (min_free_bytes_monitoring.values[counter] > old_minimum) {
min_free_bytes_monitoring.values[counter] = old_minimum;
if (min_free_bytes_monitoring.values[counter] > old_minimum) {
min_free_bytes_monitoring.values[counter] = old_minimum;
}
}
heap = SLIST_NEXT(heap, next);
@@ -367,7 +369,9 @@ esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void)
MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux);
heap_t *heap = SLIST_FIRST(&registered_heaps);
for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) {
multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]);
if (heap->heap != NULL) {
multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]);
}
heap = SLIST_NEXT(heap, next);
}

View File

@@ -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
*/
@@ -19,6 +19,9 @@
static const char *TAG = "heap_init";
/* Flag indicating if the system is in startup */
static bool s_in_startup = true;
/* Linked-list of registered heaps */
struct registered_heap_ll registered_heaps;
@@ -88,6 +91,11 @@ void heap_caps_enable_nonos_stack_heaps(void)
}
}
}
/* heap_caps_enable_nonos_stack_heaps is called from main_task right before
* app_main is called so setting this variable here is as close as we can get
* within the heap component to the actual start of the application */
s_in_startup = false;
}
/* Initialize the heap allocator to use all of the memory not
@@ -314,6 +322,15 @@ esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start,
}
multi_heap_set_lock(p_new->heap, &p_new->heap_mux);
if (!s_in_startup) {
/* Set minimum_free_bytes to 0 so the newly added heap does not
* artificially inflate minimum free size. Only perform this operation
* for heaps created after startup. The heaps created by IDF component
* before app_main is reached should be taken into account in the calculation
* of the minimum free size. */
multi_heap_restore_minimum_free_bytes(p_new->heap, 0);
}
/* (This insertion is atomic to registered_heaps, so
we don't need to worry about thread safety for readers,
only for writers. */

View File

@@ -230,6 +230,9 @@ size_t heap_caps_get_free_size( uint32_t caps );
* tracked per-region. Individual regions' heaps may have reached their "low watermarks" at different points in time. However,
* this result still gives a "worst case" indication for all-time minimum free heap.
*
* @note Heaps added at runtime using heap_caps_add_region_with_caps() (i.e., from app_main onwards) are not taken into
* account in the minimum free size calculation.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*

View File

@@ -12,6 +12,8 @@
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_heap_caps_init.h"
#include "heap_memory_layout.h"
#include "spi_flash_mmap.h"
#include "esp_memory_utils.h"
#include "esp_private/spi_flash_os.h"
@@ -206,6 +208,41 @@ TEST_CASE("heap caps minimum free bytes monitoring", "[heap]")
TEST_ASSERT(local_minimum_free_size >= free_size);
}
extern void set_leak_threshold(int threshold);
/* NOTE: This is not a well-formed unit test, it leaks memory */
TEST_CASE("heap registered after startup should not affect minimum free size", "[heap]")
{
printf("heap registered after startup should not affect minimum free size\n");
const uint32_t MALLOC_CAP_INVENTED = (1 << 29); /* unused capability, must differ from (1 << 30) used in test_runtime_heap_reg.c */
const size_t BUF_SZ = 3500;
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS] = { MALLOC_CAP_INVENTED };
// Record the minimum free size for default caps before adding a new heap
size_t minimum_before = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
// Allocate a buffer and register it as a new heap region.
// Since we are past startup (app_main has been reached), the newly
// registered heap should NOT affect the minimum free size.
void *buffer = malloc(BUF_SZ);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ESP_OK(heap_caps_add_region_with_caps(caps, (intptr_t)buffer, (intptr_t)buffer + BUF_SZ));
// The minimum free size for the invented capability should be 0
// because the heap was registered after startup.
size_t minimum_invented = heap_caps_get_minimum_free_size(MALLOC_CAP_INVENTED);
TEST_ASSERT_EQUAL(0, minimum_invented);
// The minimum free size for default caps should not have increased
// (it may have decreased slightly due to the malloc above).
size_t minimum_after = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
TEST_ASSERT(minimum_after <= minimum_before);
// set the leak threshold to a bigger value as this test leaks memory
set_leak_threshold(-4000);
}
TEST_CASE("heap caps minimum free bytes fault cases", "[heap]")
{
printf("heap caps minimum free bytes fault cases\n");

View File

@@ -19,7 +19,7 @@ To obtain information about the state of the heap, call the following functions:
- :cpp:func:`heap_caps_get_free_size` can be used to return the current free memory for different memory capabilities.
- :cpp:func:`heap_caps_get_largest_free_block` can be used to return the largest free block in the heap, which is also the largest single allocation currently possible. Tracking this value and comparing it to the total free heap allows you to detect heap fragmentation.
- :cpp:func:`heap_caps_get_minimum_free_size` can be used to track the heap "low watermark" since boot.
- :cpp:func:`heap_caps_get_minimum_free_size` can be used to track the heap "low watermark" across heaps registered during startup. Heaps added at runtime using :cpp:func:`heap_caps_add_region_with_caps` (i.e., from ``app_main`` onwards) are not taken into account.
- :cpp:func:`heap_caps_get_info` returns a :cpp:class:`multi_heap_info_t` structure, which contains the information from the above functions, plus some additional heap-specific data (number of allocations, etc.).
- :cpp:func:`heap_caps_print_heap_info` prints a summary of the information returned by :cpp:func:`heap_caps_get_info` to stdout.
- :cpp:func:`heap_caps_dump` and :cpp:func:`heap_caps_dump_all` output detailed information about the structure of each block in the heap. Note that this can be a large amount of output.