Merge branch 'contrib/github_pr_18724_v6.1' into 'release/v6.1'

fix(heap_task_info): Fix incorrect current_usage subtraction on realloc() (GitHub PR) (v6.1)

See merge request espressif/esp-idf!50276
This commit is contained in:
Marius Vikhammer
2026-08-27 16:24:06 +08:00
2 changed files with 144 additions and 3 deletions
+10 -3
View File
@@ -338,14 +338,12 @@ HEAP_IRAM_ATTR void heap_caps_update_per_task_info_realloc(heap_t *heap, void *o
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if (task_info->task_stat.handle == old_task) {
heap_stats_t *heap_stats = NULL;
task_info->task_stat.overall_current_usage -= old_size;
STAILQ_FOREACH(heap_stats, &task_info->heaps_stats, next_heap_stat) {
if (heap_stats->heap == heap->heap) {
heap_stats->heap_stat.current_usage -= old_size;
heap_stats->heap_stat.alloc_count--;
/* remove the alloc from the list. The updated alloc stats are added later
* in the function */
alloc_stat = NULL;
STAILQ_FOREACH(alloc_stat, &heap_stats->allocs_stats, next_alloc_stat) {
if (alloc_stat->alloc_stat.address == old_ptr) {
STAILQ_REMOVE(&heap_stats->allocs_stats, alloc_stat, alloc_stats, next_alloc_stat);
@@ -354,6 +352,13 @@ HEAP_IRAM_ATTR void heap_caps_update_per_task_info_realloc(heap_t *heap, void *o
break;
}
}
if (alloc_stat != NULL) {
heap_stats->heap_stat.current_usage -= old_size;
heap_stats->heap_stat.alloc_count--;
task_info->task_stat.overall_current_usage -= old_size;
}
break;
}
}
@@ -430,6 +435,8 @@ HEAP_IRAM_ATTR void heap_caps_update_per_task_info_free(heap_t *heap, void *ptr)
heap_stats->heap_stat.current_usage -= alloc_stat->alloc_stat.size;
task_info->task_stat.overall_current_usage -= alloc_stat->alloc_stat.size;
}
break;
}
}
@@ -6,12 +6,15 @@
#include "unity.h"
#include "stdio.h"
#include <string.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_caps.h"
#include "esp_heap_task_info.h"
extern void set_leak_threshold(int threshold);
// This test only apply when task tracking is enabled
#if defined(CONFIG_HEAP_TASK_TRACKING) && defined(CONFIG_HEAP_TRACK_DELETED_TASKS)
@@ -248,4 +251,135 @@ TEST_CASE("heap task tracking check alloc arrays and get info on specific task",
vTaskDelete(test_task_handle);
}
typedef struct {
void *ptr;
TaskHandle_t task;
} handle_reuse_allocation_t;
#define NUM_HANDLE_REUSE_ATTEMPTS 500
static volatile handle_reuse_allocation_t s_handle_reuse_allocations[NUM_HANDLE_REUSE_ATTEMPTS];
static volatile bool s_handle_reuse_found = false;
static volatile TaskHandle_t s_reused_task_handle = NULL;
static volatile bool s_alloc_task_done = false;
static bool task_usage_underflowed(size_t usage)
{
/* size_t underflow looks negative when printed with %d (e.g. -16 -> 0xFFFFFFF0). */
return usage > ((size_t)INT32_MAX);
}
static void assert_no_underflow_for_reused_handle(TaskHandle_t handle)
{
heap_all_tasks_stat_t tasks_stat;
esp_err_t ret_val = heap_caps_alloc_all_task_stat_arrays(&tasks_stat);
TEST_ASSERT_EQUAL(ESP_OK, ret_val);
ret_val = heap_caps_get_all_task_stat(&tasks_stat);
TEST_ASSERT_EQUAL(ESP_OK, ret_val);
size_t matching = 0;
for (size_t task_index = 0; task_index < tasks_stat.task_count; task_index++) {
task_stat_t task_stat = tasks_stat.stat_arr[task_index];
if (task_stat.handle != handle) {
continue;
}
matching++;
TEST_ASSERT_FALSE_MESSAGE(task_usage_underflowed(task_stat.overall_current_usage),
"overall_current_usage underflowed");
for (size_t heap_index = 0; heap_index < task_stat.heap_count; heap_index++) {
TEST_ASSERT_FALSE_MESSAGE(task_usage_underflowed(task_stat.heap_stat[heap_index].current_usage),
"heap current_usage underflowed");
}
}
TEST_ASSERT_GREATER_THAN(0, matching);
heap_caps_free_all_task_stat_arrays(&tasks_stat);
}
static void realloc_then_free(void *ptr, size_t size)
{
void *new_ptr = heap_caps_realloc(ptr, size, MALLOC_CAP_DEFAULT);
if (new_ptr != NULL) {
ptr = new_ptr;
}
heap_caps_free(ptr);
}
static void handle_reuse_dummy_task(void *args)
{
(void)args;
while (1) {
taskYIELD();
}
}
static void handle_reuse_alloc_task(void *args)
{
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
size_t i = (size_t)args;
s_handle_reuse_allocations[i].ptr = heap_caps_malloc(10, MALLOC_CAP_DEFAULT);
if (s_handle_reuse_allocations[i].ptr == NULL) {
abort();
}
s_handle_reuse_allocations[i].task = handle;
for (size_t j = 0; j < i; j++) {
if (s_handle_reuse_allocations[j].task == handle) {
/* Same TaskHandle_t as a previously deleted task: exercise the realloc path. */
realloc_then_free(s_handle_reuse_allocations[i].ptr, 20);
realloc_then_free(s_handle_reuse_allocations[j].ptr, 20);
s_handle_reuse_allocations[i].ptr = NULL;
s_handle_reuse_allocations[j].ptr = NULL;
s_handle_reuse_found = true;
s_reused_task_handle = handle;
s_alloc_task_done = true;
vTaskDelete(NULL);
return;
}
}
s_alloc_task_done = true;
vTaskDelete(NULL);
}
/* Reproduce the scenario from GoeBachmann/esp-idf (heap-tracking-bug branch),
* examples/system/heap_task_tracking/basic: create many short-lived tasks until
* a new task reuses a deleted task's TaskHandle_t, then realloc/free allocations
* from both lifetimes. Without the fix, overall_current_usage underflows and
* appears negative in logs.
*/
TEST_CASE("heap task tracking realloc with reused TaskHandle does not underflow usage", "[heap]")
{
TaskHandle_t dummy_task_handle = NULL;
set_leak_threshold(-50000);
s_handle_reuse_found = false;
s_reused_task_handle = NULL;
memset((void *)s_handle_reuse_allocations, 0, sizeof(s_handle_reuse_allocations));
xTaskCreate(&handle_reuse_dummy_task, "dummy_task", 3072, NULL, 0, &dummy_task_handle);
for (size_t i = 0; i < NUM_HANDLE_REUSE_ATTEMPTS; i++) {
s_alloc_task_done = false;
xTaskCreate(&handle_reuse_alloc_task, "alloc_task", 3072, (void *)i, 5, NULL);
while (!s_alloc_task_done) {
vTaskDelay(pdMS_TO_TICKS(50));
}
if (s_handle_reuse_found) {
break;
}
}
TEST_ASSERT_TRUE_MESSAGE(s_handle_reuse_found,
"Could not force TaskHandle reuse within iteration limit");
assert_no_underflow_for_reused_handle(s_reused_task_handle);
vTaskDelete(dummy_task_handle);
}
#endif // CONFIG_HEAP_TASK_TRACKING