From 8b840e8517ec2a60b35818782f2a3dd140f7a5bf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2026 11:31:13 -0500 Subject: [PATCH] [core] Also shrink items_/to_add_/defer_queue_ vector capacity in trim_freelist() The freelist holds the boot-peak count of recycled SchedulerItem*; items_, to_add_, and defer_queue_ hold the boot-peak vector *capacity* of live SchedulerItem*. std::vector grows by doubling and retains capacity even when items drain, so post-boot the vector slack can be larger than the freelist itself. Swap each with a same-content copy to size them exactly to their current contents. Live items are preserved -- the swap-copy idiom builds the new vector from the existing pointers, then the old (over-capacity) vector is destroyed. --- esphome/core/scheduler.cpp | 10 ++++++++++ esphome/core/scheduler.h | 9 ++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index c9ed69a80c..2cf6644c3e 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -914,6 +914,16 @@ void Scheduler::trim_freelist() { } this->scheduler_item_pool_head_ = nullptr; this->scheduler_item_pool_size_ = 0; + + // The vectors that back items_/to_add_/defer_queue_ also retain their boot-peak + // capacity (std::vector grows by doubling). Swap each with a same-content copy to + // reclaim the slack -- the new vector is sized exactly to its current contents. + std::vector(this->items_).swap(this->items_); + std::vector(this->to_add_).swap(this->to_add_); +#ifndef ESPHOME_THREAD_SINGLE + std::vector(this->defer_queue_).swap(this->defer_queue_); +#endif + #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Freelist trimmed (%zu items freed)", freed); #else diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index da15279ae9..7cdb29aae0 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -132,11 +132,10 @@ class Scheduler { // @return Timestamp of the last item that ran, or `now` unchanged if none ran. uint32_t call(uint32_t now); - // Free every SchedulerItem currently sitting in the recycle freelist. Items in - // items_/to_add_/defer_queue_ are untouched. Intended to claim the post-boot - // peak: boot churn (component setup, first sensor reads, retries) inflates the - // freelist beyond what steady-state needs, and without a one-shot trim that - // peak would be retained forever. + // Reclaim memory held by the post-boot peak. Frees every SchedulerItem in the + // recycle freelist and shrinks items_/to_add_/defer_queue_ vector capacity to + // their current sizes (std::vector grows by doubling and otherwise retains the + // peak). Live items in those vectors are preserved. void trim_freelist(); // Move items from to_add_ into the main heap.