diff --git a/esphome/core/lwip_fast_select.c b/esphome/core/lwip_fast_select.c index 7995a60f8d..cdbd32c8e6 100644 --- a/esphome/core/lwip_fast_select.c +++ b/esphome/core/lwip_fast_select.c @@ -128,6 +128,10 @@ static void esphome_socket_event_callback(struct netconn *conn, enum netconn_evt void esphome_lwip_fast_select_init(void) { s_main_loop_task = xTaskGetCurrentTaskHandle(); } bool esphome_lwip_socket_has_data(int fd) { + // lwip_socket_dbg_get_socket() is a direct array lookup without the refcount that + // get_socket()/done_socket() uses. This is safe because the caller owns the socket + // lifetime: both has_data() and socket close happen on the main loop thread, so + // the sockets[] entry cannot be freed while we read it. struct lwip_sock *sock = lwip_socket_dbg_get_socket(fd); if (sock == NULL || sock->conn == NULL) return false; diff --git a/esphome/core/lwip_fast_select.h b/esphome/core/lwip_fast_select.h index 7d16042c03..73a89fdc3d 100644 --- a/esphome/core/lwip_fast_select.h +++ b/esphome/core/lwip_fast_select.h @@ -14,8 +14,9 @@ extern "C" { void esphome_lwip_fast_select_init(void); /// Check if a LwIP socket has data ready via direct rcvevent read (~215 ns per socket). -/// Uses lwip_socket_dbg_get_socket() which is a direct array lookup — no locking, no refcount. -/// Safe for single-threaded polling from the main loop. +/// Uses lwip_socket_dbg_get_socket() — a direct array lookup without the refcount that +/// get_socket()/done_socket() uses. Safe because the caller owns the socket lifetime: +/// both has_data reads and socket close/unregister happen on the main loop thread. bool esphome_lwip_socket_has_data(int fd); /// Hook a socket's netconn callback to notify the main loop task on receive events.