diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index b0457d15ec..1a1dfc3233 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -634,7 +634,8 @@ void Application::unregister_socket_fd(int fd) { 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. #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_ESP32) - // ESP32 fast path: no fd_set needed — is_socket_ready_() reads rcvevent directly (~215 ns per socket) + // ESP32 fast path: reads rcvevent directly via lwip_socket_dbg_get_socket() (~215 ns per socket). + // Safe because this runs on the main loop which owns socket lifetime (create, read, close). if (delay_ms == 0) [[unlikely]] { yield(); return; diff --git a/esphome/core/application.h b/esphome/core/application.h index 9bd4c99a6e..f5df5e7bdf 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -511,13 +511,12 @@ class Application { #ifdef USE_SOCKET_SELECT_SUPPORT /// Fast path for Socket::ready() via friendship - skips negative fd check. - /// Safe because: fd was validated in register_socket_fd() at registration time, - /// and Socket::ready() only calls this when loop_monitored_ is true (registration succeeded). + /// Main loop only — on ESP32, reads rcvevent via lwip_socket_dbg_get_socket() + /// which has no refcount; safe only because the main loop owns socket lifetime + /// (creates, reads, and closes sockets on the same thread). #ifdef USE_ESP32 - /// ESP32: direct rcvevent read — always fresh, no fd_set snapshot needed (~215 ns) bool is_socket_ready_(int fd) const { return esphome_lwip_socket_has_data(fd); } #else - /// Other platforms: check fd_set populated by select() bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); } #endif #endif