From 2785edaac878d59bdfe6351e317465f881f5403f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 17 Mar 2026 01:11:48 -1000 Subject: [PATCH] [core] Clamp underflowed blocking_time in warn_blocking cold path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When millis() < started_ (e.g. scheduler passes a now value slightly ahead of real millis()), the uint32_t subtraction in finish() wraps to ~4 billion. This caused warn_blocking to fire on every call since the underflowed value always exceeds the uint16_t threshold max (65535). Fix by clamping blocking_time to uint16_t max in the cold warn_blocking path. After one warning, should_warn_of_blocking() saturates the threshold to 65535 and subsequent clamped values (65535) don't exceed it. Zero cost on the hot path — the clamp is in the noinline cold function. --- esphome/core/component.cpp | 7 +++++++ esphome/core/component.h | 11 +++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index bfe9beb2723..172842ee3d4 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -512,6 +512,13 @@ void PollingComponent::set_update_interval(uint32_t update_interval) { this->upd void __attribute__((noinline, cold)) WarnIfComponentBlockingGuard::warn_blocking(Component *component, uint32_t blocking_time) { + // Clamp underflowed values: if millis() < started_ (e.g. scheduler passes + // a `now` slightly ahead of real millis()), the subtraction wraps to ~4 billion. + // Clamping to uint16_t max lets should_warn_of_blocking() saturate the + // threshold and suppress further warnings. + if (blocking_time > std::numeric_limits::max()) { + blocking_time = std::numeric_limits::max(); + } bool should_warn; if (component != nullptr) { should_warn = component->should_warn_of_blocking(blocking_time); diff --git a/esphome/core/component.h b/esphome/core/component.h index 64f99716272..5fdf23e128a 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -594,17 +594,12 @@ class WarnIfComponentBlockingGuard { // Inlined: the fast path is just millis() + subtract + compare inline uint32_t HOT finish() { uint32_t curr_time = millis(); + uint32_t blocking_time = curr_time - this->started_; #ifdef USE_RUNTIME_STATS this->record_runtime_stats_(); #endif - // Guard against underflow: if curr_time < started_, the subtraction wraps - // to a huge value. This can happen when the scheduler passes a `now` value - // slightly ahead of real millis() (e.g. from execute_item_ return values). - if (curr_time >= this->started_) [[likely]] { - uint32_t blocking_time = curr_time - this->started_; - if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] { - warn_blocking(this->component_, blocking_time); - } + if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] { + warn_blocking(this->component_, blocking_time); } return curr_time; }