diff --git a/esphome/core/lwip_fast_select.h b/esphome/core/lwip_fast_select.h index fc3f6847a2..f76eb47c8b 100644 --- a/esphome/core/lwip_fast_select.h +++ b/esphome/core/lwip_fast_select.h @@ -29,17 +29,21 @@ void esphome_lwip_fast_select_init(void); /// Use this at registration time to cache the pointer for esphome_lwip_socket_has_data(). struct lwip_sock *esphome_lwip_get_sock(int fd); -/// Check if a cached LwIP socket has data ready via direct rcvevent read. +/// Check if a cached LwIP socket has data ready via unlocked hint read of rcvevent. +/// This avoids lwIP core lock contention between the main loop (CPU0) and +/// streaming/networking work (CPU1). Correctness is preserved because sockets are +/// nonblocking and EWOULDBLOCK is handled as normal — a stale hint simply means +/// we retry on the next loop iteration. +/// /// The sock pointer must have been obtained from esphome_lwip_get_sock() and must /// remain valid (caller owns socket lifetime — no concurrent close). /// Hot path: inlined volatile 16-bit load — no function call overhead. /// Uses offset-based access because lwip/priv/sockets_priv.h conflicts with C++. -/// The offset is verified at compile time in lwip_fast_select.c. +/// The offset and size are verified at compile time in lwip_fast_select.c. static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) { + // Unlocked hint read — no lwIP core lock needed. // volatile prevents the compiler from caching/reordering this cross-thread read. - // The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT which releases a - // FreeRTOS mutex (ESP32) or resumes the scheduler (LibreTiny), ensuring the value - // is visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH/LDRH) on + // Aligned 16-bit reads are single-instruction loads (L16SI/LH/LDRH) on // Xtensa/RISC-V/ARM and cannot produce torn values. return *(volatile int16_t *) ((char *) sock + (int) ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET) > 0; }