From 121b4d6916e73b716d6f6fecf8a08a8df778e6ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 24 Apr 2026 03:08:01 -0500 Subject: [PATCH] [web_server_idf] Move session adoption to out-of-line cold path Keep the hot path in AsyncEventSource::loop() to the atomic load, branch, and dead-session cleanup. On a real connect, call a noinline+cold helper (adopt_pending_sessions_main_loop_) to swap the pending list out, drop any already-disconnected sessions, and invoke on_connect_ / start_session_main_loop_ on the rest. --- .../web_server_idf/web_server_idf.cpp | 42 ++++++++++--------- .../web_server_idf/web_server_idf.h | 3 ++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 66942463e6..22a8094424 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -493,26 +493,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) { } bool AsyncEventSource::loop() { - // Fast path: one atomic load per tick. Lock only on a real connect. + // Fast path: one atomic load per tick. Slow path is out-of-line on connect. if (this->has_pending_sessions_.load(std::memory_order_acquire)) { - std::vector incoming; - { - LockGuard guard{this->pending_mutex_}; - incoming.swap(this->pending_sessions_); - this->has_pending_sessions_.store(false, std::memory_order_relaxed); - } - for (auto *rsp : incoming) { - // Already disconnected? Drop it; skip on_connect_/prime on a dead session. - if (rsp->fd_.load() == 0) { - delete rsp; // NOLINT(cppcoreguidelines-owning-memory) - continue; - } - this->sessions_.push_back(rsp); - if (this->on_connect_) { - this->on_connect_(rsp); - } - rsp->start_session_main_loop_(); - } + this->adopt_pending_sessions_main_loop_(); } // Clean up dead sessions safely @@ -535,6 +518,27 @@ bool AsyncEventSource::loop() { return !this->sessions_.empty(); } +void AsyncEventSource::adopt_pending_sessions_main_loop_() { + std::vector incoming; + { + LockGuard guard{this->pending_mutex_}; + incoming.swap(this->pending_sessions_); + this->has_pending_sessions_.store(false, std::memory_order_relaxed); + } + for (auto *rsp : incoming) { + // Already disconnected? Drop it; skip on_connect_/session start on a dead session. + if (rsp->fd_.load() == 0) { + delete rsp; // NOLINT(cppcoreguidelines-owning-memory) + continue; + } + this->sessions_.push_back(rsp); + if (this->on_connect_) { + this->on_connect_(rsp); + } + rsp->start_session_main_loop_(); + } +} + void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) { for (auto *ses : this->sessions_) { if (ses->fd_.load() != 0) { // Skip dead sessions diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 89e1c5fada..742a544695 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -350,6 +350,9 @@ class AsyncEventSource : public AsyncWebHandler { size_t count() const { return this->sessions_.size(); } protected: + // Cold path: move sessions from pending_sessions_ into sessions_ and greet each one. + void __attribute__((noinline, cold)) adopt_pending_sessions_main_loop_(); + std::string url_; // Main-loop only. Vector: SSE sessions are 1-5 connections, linear search beats set. std::vector sessions_;