From d686257cfff4d2e22046bf0ac3b609b120257495 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Jul 2025 21:07:25 -0500 Subject: [PATCH] Fix web_server busy loop with ungracefully disconnected clients (#9312) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/web_server.cpp | 11 +++++++++++ esphome/components/web_server/web_server.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f576507c0f..77c20b956b 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -125,7 +125,16 @@ void DeferredUpdateEventSource::process_deferred_queue_() { if (this->send(message.c_str(), "state") != DISCARDED) { // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen deferred_queue_.erase(deferred_queue_.begin()); + this->consecutive_send_failures_ = 0; // Reset failure count on successful send } else { + this->consecutive_send_failures_++; + if (this->consecutive_send_failures_ >= MAX_CONSECUTIVE_SEND_FAILURES) { + // Too many failures, connection is likely dead + ESP_LOGW(TAG, "Closing stuck EventSource connection after %" PRIu16 " failed sends", + this->consecutive_send_failures_); + this->close(); + this->deferred_queue_.clear(); + } break; } } @@ -164,6 +173,8 @@ void DeferredUpdateEventSource::deferrable_send_state(void *source, const char * std::string message = message_generator(web_server_, source); if (this->send(message.c_str(), "state") == DISCARDED) { deq_push_back_with_dedup_(source, message_generator); + } else { + this->consecutive_send_failures_ = 0; // Reset failure count on successful send } } } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index c654d83bbd..82b31ab656 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -127,6 +127,8 @@ class DeferredUpdateEventSource : public AsyncEventSource { // footprint is more important than speed here) std::vector deferred_queue_; WebServer *web_server_; + uint16_t consecutive_send_failures_{0}; + static constexpr uint16_t MAX_CONSECUTIVE_SEND_FAILURES = 2500; // ~20 seconds at 125Hz loop rate // helper for allowing only unique entries in the queue void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);