From dac738a916689808aea642c9662b8fcda707d771 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:27:10 -0500 Subject: [PATCH] Always perform select() when loop duration exceeds interval (#9058) --- esphome/core/application.cpp | 18 ++++++++++++------ esphome/core/application.h | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 75a7052c63..87e6f33e04 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -117,7 +117,9 @@ void Application::loop() { // Use the last component's end time instead of calling millis() again auto elapsed = last_op_end_time - this->last_loop_; if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) { - yield(); + // Even if we overran the loop interval, we still need to select() + // to know if any sockets have data ready + this->yield_with_select_(0); } else { uint32_t delay_time = this->loop_interval_ - elapsed; uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); @@ -126,7 +128,7 @@ void Application::loop() { next_schedule = std::max(next_schedule, delay_time / 2); delay_time = std::min(next_schedule, delay_time); - this->delay_with_select_(delay_time); + this->yield_with_select_(delay_time); } this->last_loop_ = last_op_end_time; @@ -215,7 +217,7 @@ void Application::teardown_components(uint32_t timeout_ms) { // Give some time for I/O operations if components are still pending if (!pending_components.empty()) { - this->delay_with_select_(1); + this->yield_with_select_(1); } // Update time for next iteration @@ -293,8 +295,6 @@ bool Application::is_socket_ready(int fd) const { // This function is thread-safe for reading the result of select() // However, it should only be called after select() has been executed in the main loop // The read_fds_ is only modified by select() in the main loop - if (HighFrequencyLoopRequester::is_high_frequency()) - return true; // fd sets via select are not updated in high frequency looping - so force true fallback behavior if (fd < 0 || fd >= FD_SETSIZE) return false; @@ -302,7 +302,9 @@ bool Application::is_socket_ready(int fd) const { } #endif -void Application::delay_with_select_(uint32_t delay_ms) { +void Application::yield_with_select_(uint32_t delay_ms) { + // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run + // since select() with 0 timeout only polls without yielding. #ifdef USE_SOCKET_SELECT_SUPPORT if (!this->socket_fds_.empty()) { // Update fd_set if socket list has changed @@ -340,6 +342,10 @@ void Application::delay_with_select_(uint32_t delay_ms) { ESP_LOGW(TAG, "select() failed with errno %d", errno); delay(delay_ms); } + // When delay_ms is 0, we need to yield since select(0) doesn't yield + if (delay_ms == 0) { + yield(); + } } else { // No sockets registered, use regular delay delay(delay_ms); diff --git a/esphome/core/application.h b/esphome/core/application.h index d95f45e757..6c09b25590 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -575,7 +575,7 @@ class Application { void feed_wdt_arch_(); /// Perform a delay while also monitoring socket file descriptors for readiness - void delay_with_select_(uint32_t delay_ms); + void yield_with_select_(uint32_t delay_ms); std::vector components_{}; std::vector looping_components_{};