From 216cc47e4e0b1695d3645bcf450fd3d9b5b72478 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Mar 2026 22:07:59 -1000 Subject: [PATCH] [socket] Extract read_locked_() so readv() never calls wait_for_data_() under lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readv() holds LWIP_LOCK for the entire scatter-gather operation and previously called read() internally, which would call wait_for_data_() → socket_delay() while the lock was held — blocking recv_fn() on RP2040. Extract read_locked_() with the state checks and copy logic. Both read() and readv() call wait_for_data_() before acquiring the lock, then use read_locked_() under the lock. --- .../components/socket/lwip_raw_tcp_impl.cpp | 23 +++++++++++-------- esphome/components/socket/lwip_raw_tcp_impl.h | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 566e96b2f9..5ba98dd526 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -535,13 +535,8 @@ void LWIPRawImpl::wait_for_data_() { } } -ssize_t LWIPRawImpl::read(void *buf, size_t len) { - // See waiting_for_data_() for safety of unlocked reads. - if (this->recv_timeout_cs_ > 0 && this->waiting_for_data_()) { - this->wait_for_data_(); - } - - LWIP_LOCK(); +ssize_t LWIPRawImpl::read_locked_(void *buf, size_t len) { + // Caller must hold LWIP_LOCK. Copies available data from rx_buf_ into buf. if (this->pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -600,8 +595,18 @@ ssize_t LWIPRawImpl::read(void *buf, size_t len) { return read; } +ssize_t LWIPRawImpl::read(void *buf, size_t len) { + // See waiting_for_data_() for safety of unlocked reads. + if (this->recv_timeout_cs_ > 0 && this->waiting_for_data_()) { + this->wait_for_data_(); + } + + LWIP_LOCK(); + return this->read_locked_(buf, len); +} + ssize_t LWIPRawImpl::readv(const struct iovec *iov, int iovcnt) { - // See read() for safety analysis of these unlocked reads. + // See waiting_for_data_() for safety of unlocked reads. if (this->recv_timeout_cs_ > 0 && this->waiting_for_data_()) { this->wait_for_data_(); } @@ -609,7 +614,7 @@ ssize_t LWIPRawImpl::readv(const struct iovec *iov, int iovcnt) { LWIP_LOCK(); // Hold for entire scatter-gather operation ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { - ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + ssize_t err = this->read_locked_(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { if (ret != 0) { // if we already read some don't return an error diff --git a/esphome/components/socket/lwip_raw_tcp_impl.h b/esphome/components/socket/lwip_raw_tcp_impl.h index 6007852692..3c27d71062 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.h +++ b/esphome/components/socket/lwip_raw_tcp_impl.h @@ -127,6 +127,7 @@ class LWIPRawImpl : public LWIPRawCommon { // authoritative recheck happens under LWIP_LOCK afterward. bool waiting_for_data_() const { return this->rx_buf_ == nullptr && !this->rx_closed_ && this->pcb_ != nullptr; } void wait_for_data_(); + ssize_t read_locked_(void *buf, size_t len); ssize_t internal_write_(const void *buf, size_t len); int internal_output_();