Merge remote-tracking branch 'upstream/feed-wdt-inline-fastpath' into integration

This commit is contained in:
J. Nick Koston
2026-04-11 15:22:33 -10:00
3 changed files with 44 additions and 38 deletions
+32 -27
View File
@@ -196,35 +196,40 @@ 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) {
auto *sl = status_led::global_status_led;
uint8_t sl_state = sl->get_component_state() & COMPONENT_STATE_MASK;
if (sl_state == COMPONENT_STATE_LOOP_DONE) {
// status_led only transitions to LOOP_DONE from inside its own loop() (after the
// first idle-path dispatch), so its pin is already initialized by pre_setup() and
// its setup() has already run. Re-dispatch only if an error or warning bit has been
// set since; otherwise skip entirely.
if ((this->app_state_ & STATUS_LED_MASK) == 0)
return;
sl->enable_loop();
} else if (sl_state != COMPONENT_STATE_LOOP) {
// CONSTRUCTION/SETUP/FAILED: not our job — App::setup() drives the lifecycle.
return;
}
sl->loop();
}
#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) {
auto *sl = status_led::global_status_led;
uint8_t sl_state = sl->get_component_state() & COMPONENT_STATE_MASK;
if (sl_state == COMPONENT_STATE_LOOP_DONE) {
// status_led only transitions to LOOP_DONE from inside its own loop() (after the
// first idle-path dispatch), so its pin is already initialized by pre_setup() and
// its setup() has already run. Re-dispatch only if an error or warning bit has been
// set since; otherwise skip entirely.
if ((this->app_state_ & STATUS_LED_MASK) == 0)
return;
sl->enable_loop();
} else if (sl_state != COMPONENT_STATE_LOOP) {
// CONSTRUCTION/SETUP/FAILED: not our job — App::setup() drives the lifecycle.
return;
}
sl->loop();
}
#endif
}
void Application::reboot() {
ESP_LOGI(TAG, "Forcing a reboot");
for (auto &component : std::ranges::reverse_view(this->components_)) {
@@ -313,7 +318,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;
+11 -10
View File
@@ -234,15 +234,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<uint32_t>(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<uint32_t>(time - this->last_wdt_feed_) > WDT_FEED_INTERVAL_MS) [[unlikely]] {
this->feed_wdt_slow_(time);
}
}
@@ -575,7 +576,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_();
+1 -1
View File
@@ -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;
}