diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 84a1fc346fe..36038c64400 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -196,21 +196,26 @@ void Application::process_dump_config_() { this->dump_config_at_++; } -void HOT Application::feed_wdt_slow_(uint32_t time) { - // Use provided time if available, otherwise get current time - uint32_t now = time ? time : millis(); - // The inline wrapper already performs this check when time != 0; - // repeat it here for the time == 0 entry and as a safety net. +void Application::feed_wdt() { + // Cold entry: callers without a millis() timestamp in hand. Fetches the + // time and takes the same rate-limit path as feed_wdt_with_time(). + uint32_t now = millis(); if (now - this->last_wdt_feed_ > WDT_FEED_INTERVAL_MS) { - arch_feed_wdt(); - this->last_wdt_feed_ = now; -#ifdef USE_STATUS_LED - if (status_led::global_status_led != nullptr) { - status_led::global_status_led->call(); - } -#endif + this->feed_wdt_slow_(now); } } + +void HOT Application::feed_wdt_slow_(uint32_t time) { + // Callers (both feed_wdt() and feed_wdt_with_time()) have already + // confirmed the 3 ms rate limit was exceeded. + arch_feed_wdt(); + this->last_wdt_feed_ = time; +#ifdef USE_STATUS_LED + if (status_led::global_status_led != nullptr) { + status_led::global_status_led->call(); + } +#endif +} void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot"); for (auto &component : std::ranges::reverse_view(this->components_)) { @@ -299,7 +304,7 @@ void Application::teardown_components(uint32_t timeout_ms) { while (pending_count > 0 && (now - start_time) < timeout_ms) { // Feed watchdog during teardown to prevent triggering - this->feed_wdt(now); + this->feed_wdt_with_time(now); // Process components and compact the array, keeping only those still pending size_t still_pending = 0; diff --git a/esphome/core/application.h b/esphome/core/application.h index 02af5664a45..52b976b8bdf 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -390,15 +390,16 @@ class Application { /// watchdog timeout (seconds) has orders of magnitude of safety margin. static constexpr uint32_t WDT_FEED_INTERVAL_MS = 3; - /// Feed the task watchdog. Hot-path inline rate-limit check: callers that - /// already have a timestamp in hand pay only a load + sub + branch on the - /// common (no-op) path. The actual arch feed + status LED update live in - /// feed_wdt_slow_ to keep this small enough to inline freely. - /// - /// Pass time==0 to request millis() be read for you (low-frequency callers - /// only — always takes the slow path). - void ESPHOME_ALWAYS_INLINE feed_wdt(uint32_t time = 0) { - if (time == 0 || static_cast(time - this->last_wdt_feed_) > WDT_FEED_INTERVAL_MS) { + /// Feed the task watchdog. Cold entry — callers without a millis() + /// timestamp in hand. Out of line to keep call sites tiny. + void feed_wdt(); + + /// Feed the task watchdog, hot entry. Callers that already have a + /// millis() timestamp pay only a load + sub + branch on the common + /// (no-op) path. The actual arch feed + status LED update live in + /// feed_wdt_slow_. + void ESPHOME_ALWAYS_INLINE feed_wdt_with_time(uint32_t time) { + if (static_cast(time - this->last_wdt_feed_) > WDT_FEED_INTERVAL_MS) [[unlikely]] { this->feed_wdt_slow_(time); } } @@ -882,7 +883,7 @@ inline void ESPHOME_ALWAYS_INLINE Application::loop() { } new_app_state |= component->get_component_state(); this->app_state_ |= new_app_state; - this->feed_wdt(last_op_end_time); + this->feed_wdt_with_time(last_op_end_time); } this->after_loop_tasks_(); diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7c9263583dd..3e75a680643 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -744,7 +744,7 @@ uint32_t HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) { // queue paths go through here). A run of back-to-back callbacks cannot // starve the wdt. The inline fast path is a load + sub + branch — nearly // free when the 3 ms rate limit hasn't elapsed. - App.feed_wdt(end); + App.feed_wdt_with_time(end); return end; }