From ce6332e6c3128d3baeeaeb918bc0405cb6975159 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Mar 2026 11:12:15 -1000 Subject: [PATCH] [core] Remove pre-sleep socket scan from fast select path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-sleep scan of all monitored sockets was added to preserve select() semantics by checking for pending data before sleeping. However, this is unnecessary with the FreeRTOS task notification approach: - xTaskNotifyGive from the lwip callback persists until consumed by ulTaskNotifyTake, so notifications received while the task is running (not sleeping) are not lost. - The only case the scan caught was intentionally undrained sockets (e.g., API's MAX_MESSAGES_PER_LOOP=5 throttle). Adding up to 16ms (loop_interval) latency before re-checking undrained data is the desired behavior — waking immediately would defeat the purpose of the throttle which exists to let other components run. This removes N volatile cross-core reads (one per monitored socket) from every loop iteration. --- esphome/core/application.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index db1c8a0c0a..6b93ce28f0 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -651,21 +651,16 @@ void Application::yield_with_select_(uint32_t delay_ms) { return; } - // Check if any socket already has pending data before sleeping. - // If a socket still has unread data (rcvevent > 0) but the task notification was already - // consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency. - // This scan preserves select() semantics: return immediately when any fd is ready. - for (struct lwip_sock *sock : this->monitored_sockets_) { - if (esphome_lwip_socket_has_data(sock)) { - yield(); - return; - } - } - // Sleep with instant wake via FreeRTOS task notification. // Woken by: callback wrapper (socket data arrives), wake_loop_threadsafe() (other tasks), or timeout. // Without USE_WAKE_LOOP_THREADSAFE, only hooked socket callbacks wake the task — // background tasks won't call wake, so this degrades to a pure timeout (same as old select path). + // + // No pre-sleep socket scan needed: xTaskNotifyGive from the lwip callback persists + // until consumed by ulTaskNotifyTake, so notifications received while the task is + // running are not lost. The only unhandled case is intentionally undrained sockets + // (e.g., API's MAX_MESSAGES_PER_LOOP throttle), where the delay_ms latency before + // re-checking is the desired behavior — waking immediately would defeat the throttle. ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms)); #elif defined(USE_SOCKET_SELECT_SUPPORT)