From 5aa9c18dfc67ed7030fd2375a624258cadc5194f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Mar 2026 21:49:06 -1000 Subject: [PATCH] [ota,socket] Add SO_SNDTIMEO and use delay(0) in readall_ - Add SO_SNDTIMEO to OTA socket to prevent blocking writes from stalling the WDT when the TCP send buffer is full - Add SO_SNDTIMEO as no-op in raw TCP (writes never block) - Use delay(0) instead of delay(1) in readall_() since SO_RCVTIMEO already handles the wait - Keep delay(1) in writeall_() since raw TCP writes are non-blocking and would spin on EWOULDBLOCK without it --- esphome/components/esphome/ota/ota_esphome.cpp | 7 ++++++- esphome/components/socket/headers.h | 1 + esphome/components/socket/lwip_raw_tcp_impl.cpp | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index b84bfe67917..e955dbf1b1c 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -258,6 +258,9 @@ void ESPHomeOTAComponent::handle_data_() { tv.tv_sec = 2; tv.tv_usec = 0; this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + // Also set send timeout to prevent blocking writes from stalling the WDT + // when the TCP send buffer is full (e.g., network congestion). + this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); this->client_->setblocking(true); // Acknowledge auth OK - 1 byte @@ -413,7 +416,9 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { } else { at += read; } - this->yield_and_feed_watchdog_(); + // read() already waited via SO_RCVTIMEO, just yield without 1ms stall + App.feed_wdt(); + delay(0); } return true; diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index c3f7e1e0467..0eece6480f6 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -52,6 +52,7 @@ #define SO_KEEPALIVE 0x0008 /* keep connections alive */ #define SO_BROADCAST 0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */ #define SO_RCVTIMEO 0x1006 /* receive timeout */ +#define SO_SNDTIMEO 0x1005 /* send timeout */ #define SOL_SOCKET 0xfff /* options for socket level */ diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 7995e83d245..8fb11c6c78b 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -355,6 +355,10 @@ int LWIPRawCommon::setsockopt(int level, int optname, const void *optval, sockle this->recv_timeout_cs_ = cs > 255 ? 255 : static_cast(cs); return 0; } + if (level == SOL_SOCKET && optname == SO_SNDTIMEO) { + // Raw TCP writes are non-blocking (tcp_write), so send timeout is a no-op. + return 0; + } if (level == IPPROTO_TCP && optname == TCP_NODELAY) { if (optlen != 4) { errno = EINVAL;