From f29da222fe27993ca350f942b09357d085d2cd70 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 18 Apr 2026 17:01:17 -0500 Subject: [PATCH] [core] raise WDT_FEED_INTERVAL_MS from 3 to 300 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3 ms rate limit was tight enough that the outer feed_wdt_with_time() at the top of Application::loop() hit the slow path on nearly every iteration. On-device measurements showed 95-99.9% of iterations triggering esp_task_wdt_reset(), costing ~9-12 us/hit (BT proxies worse due to WDT spinlock contention with the BT task on the other core). Much worse under wake-storm workloads: with loop_interval_ raised for power saving and an external stack (OpenThread in the reported case) posting frequent wake notifications, the main loop can spin at thousands of Hz doing almost nothing but feeding the watchdog — burning ~26% CPU in the extreme case reported on #15792. Raising the threshold to 300 ms: - Normal 62 Hz loop: feeds once every ~19 iterations (~3 Hz) instead of every iteration. - Wake-storm case: feeds ~3 Hz regardless of wake rate. - Any operation exceeding 300 ms still triggers a real feed right after it finishes (the post-component and post-scheduler-item feeds naturally clear the gate). Safety margins vs platform watchdog timeouts remain large: 16x on ESP32 (5 s task WDT), 5x on ESP8266 soft WDT (1.6 s), 20x on ESP8266 HW WDT. --- esphome/core/application.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index 037789ae8b..65fb8b1e4e 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -229,10 +229,17 @@ class Application { void schedule_dump_config() { this->dump_config_at_ = 0; } - /// Minimum interval between real arch_feed_wdt() calls. Chosen to keep the - /// rate of HAL pokes low while still being small enough that any plausible - /// watchdog timeout (seconds) has orders of magnitude of safety margin. - static constexpr uint32_t WDT_FEED_INTERVAL_MS = 3; + /// Minimum interval between real arch_feed_wdt() calls. Sized so the outer + /// feed in Application::loop() is effectively rate-limited across both the + /// normal ~62 Hz cadence and worst-case wake-storm scenarios (e.g. external + /// stacks like OpenThread posting frequent wake notifications). Component + /// loops and scheduler items still feed after every op, so any op exceeding + /// this threshold triggers a real feed naturally. + /// Safety margins vs. platform watchdog timeouts: + /// - ESP32 task WDT default (5 s): ~16x + /// - ESP8266 soft WDT (~1.6 s): ~5x + /// - ESP8266 HW WDT (~6 s): ~20x + static constexpr uint32_t WDT_FEED_INTERVAL_MS = 300; /// Feed the task watchdog. Cold entry — callers without a millis() /// timestamp in hand. Out of line to keep call sites tiny.