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");