From 427a49959a886ab80d0c85ab6c69aea4cd947896 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Mar 2026 08:16:42 -1000 Subject: [PATCH 1/3] [mqtt] Rate-limit component resends to prevent task WDT on reconnect When MQTT reconnects, all components have their discovery and state republished. With many components, processing all of them in a single loop iteration blocks the main loop long enough to trigger the task watchdog timer. Limit to 4 resends per loop iteration to spread the work across multiple cycles. Closes https://github.com/esphome/esphome/issues/15057 --- esphome/components/mqtt/mqtt_client.cpp | 17 ++++++++++++++--- esphome/components/mqtt/mqtt_component.h | 3 +++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 38daf8f8f6f..0acbbf3eb5b 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -28,6 +28,10 @@ namespace esphome::mqtt { static const char *const TAG = "mqtt"; +// Maximum number of MQTT component resends per loop iteration. +// Limits work to avoid triggering the task watchdog on reconnect. +static constexpr uint8_t MAX_RESENDS_PER_LOOP = 4; + // Disconnect reason strings indexed by MQTTClientDisconnectReason enum (0-8) PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings, "TCP disconnected", "Unacceptable Protocol Version", "Identifier Rejected", "Server Unavailable", "Malformed Credentials", "Not Authorized", @@ -396,9 +400,16 @@ void MQTTClientComponent::loop() { this->resubscribe_subscriptions_(); // Process pending resends for all MQTT components centrally - // This is more efficient than each component polling in its own loop - for (MQTTComponent *component : this->children_) { - component->process_resend(); + // Limit work per loop iteration to avoid triggering task WDT on reconnect + { + uint8_t resend_count = 0; + for (MQTTComponent *component : this->children_) { + if (component->resend_pending()) { + component->process_resend(); + if (++resend_count >= MAX_RESENDS_PER_LOOP) + break; + } + } } } break; diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 2403ef64ea3..a8afe75504c 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -147,6 +147,9 @@ class MQTTComponent : public Component { /// Internal method for the MQTT client base to schedule a resend of the state on reconnect. void schedule_resend_state(); + /// Check if a resend is pending (called by MQTTClientComponent to rate-limit work) + bool resend_pending() const { return this->resend_state_; } + /// Process pending resend if needed (called by MQTTClientComponent) void process_resend(); From 2b95852b6c5abe45e45daa43977dec3a333edb38 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Mar 2026 08:26:23 -1000 Subject: [PATCH 2/3] Rename resend_pending() to is_resend_pending() for consistency --- esphome/components/mqtt/mqtt_client.cpp | 2 +- esphome/components/mqtt/mqtt_component.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 0acbbf3eb5b..5793f63e2a5 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -404,7 +404,7 @@ void MQTTClientComponent::loop() { { uint8_t resend_count = 0; for (MQTTComponent *component : this->children_) { - if (component->resend_pending()) { + if (component->is_resend_pending()) { component->process_resend(); if (++resend_count >= MAX_RESENDS_PER_LOOP) break; diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index a8afe75504c..7983e04870e 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -148,7 +148,7 @@ class MQTTComponent : public Component { void schedule_resend_state(); /// Check if a resend is pending (called by MQTTClientComponent to rate-limit work) - bool resend_pending() const { return this->resend_state_; } + bool is_resend_pending() const { return this->resend_state_; } /// Process pending resend if needed (called by MQTTClientComponent) void process_resend(); From cb2fef8b6cfd028c02634e1a43daf88039cab980 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Mar 2026 08:27:45 -1000 Subject: [PATCH 3/3] Increase MAX_RESENDS_PER_LOOP from 4 to 8 --- esphome/components/mqtt/mqtt_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 5793f63e2a5..ab665e2579a 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -30,7 +30,7 @@ static const char *const TAG = "mqtt"; // Maximum number of MQTT component resends per loop iteration. // Limits work to avoid triggering the task watchdog on reconnect. -static constexpr uint8_t MAX_RESENDS_PER_LOOP = 4; +static constexpr uint8_t MAX_RESENDS_PER_LOOP = 8; // Disconnect reason strings indexed by MQTTClientDisconnectReason enum (0-8) PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings, "TCP disconnected", "Unacceptable Protocol Version",