From 8cedf8f88231a9cd00d8a3a1d008ab3eb4940668 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Feb 2026 23:03:53 -1000 Subject: [PATCH] Restore comments from original code to minimize diff --- .../components/socket/lwip_raw_tcp_impl.cpp | 15 +++++++++++++ esphome/components/socket/lwip_raw_tcp_impl.h | 21 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index f887633278..6556979fe0 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -330,6 +330,11 @@ void LWIPRawImpl::init() { } void LWIPRawImpl::s_err_fn(void *arg, err_t err) { + // "If a connection is aborted because of an error, the application is alerted of this event by + // the err callback." + // pcb is already freed when this callback is called + // ERR_RST: connection was reset by remote host + // ERR_ABRT: aborted through tcp_abort or TCP timer auto *arg_this = reinterpret_cast(arg); ESP_LOGVV(TAG, "socket %p: err(err=%d)", arg_this, err); arg_this->pcb_ = nullptr; @@ -343,6 +348,8 @@ err_t LWIPRawImpl::s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, er err_t LWIPRawImpl::recv_fn(struct pbuf *pb, err_t err) { LWIP_LOG("recv(pb=%p err=%d)", pb, err); if (err != 0) { + // "An error code if there has been an error receiving Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" this->rx_closed_ = true; return ERR_OK; } @@ -477,6 +484,10 @@ int LWIPRawImpl::internal_output_() { LWIP_LOG("tcp_output(%p)", this->pcb_); err_t err = tcp_output(this->pcb_); if (err == ERR_ABRT) { + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context LWIP_LOG(" -> err ERR_ABRT"); return 0; } @@ -604,6 +615,10 @@ int LWIPRawListenImpl::listen(int backlog) { err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) { LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); if (err != ERR_OK || newpcb == nullptr) { + // "An error code if there has been an error accepting. Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d + // nothing to do here, we just don't push it to the queue return ERR_OK; } // Check if we've reached the maximum accept queue size diff --git a/esphome/components/socket/lwip_raw_tcp_impl.h b/esphome/components/socket/lwip_raw_tcp_impl.h index 2070f9c749..c171e0537f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.h +++ b/esphome/components/socket/lwip_raw_tcp_impl.h @@ -68,6 +68,7 @@ class LWIPRawImpl : public LWIPRawCommon { void init(); + // Non-listening sockets return error std::unique_ptr accept(struct sockaddr *, socklen_t *) { errno = EINVAL; return nullptr; @@ -75,6 +76,8 @@ class LWIPRawImpl : public LWIPRawCommon { std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { return this->accept(addr, addrlen); } + // Regular sockets can't be converted to listening - this shouldn't happen + // as listen() should only be called on sockets created for listening int listen(int) { errno = EOPNOTSUPP; return -1; @@ -88,6 +91,7 @@ class LWIPRawImpl : public LWIPRawCommon { ssize_t write(const void *buf, size_t len); ssize_t writev(const struct iovec *iov, int iovcnt); ssize_t sendto(const void *, size_t, int, const struct sockaddr *, socklen_t) { + // return ::sendto(fd_, buf, len, flags, to, tolen); errno = ENOSYS; return -1; } @@ -99,6 +103,7 @@ class LWIPRawImpl : public LWIPRawCommon { return -1; } if (blocking) { + // blocking operation not supported errno = EINVAL; return -1; } @@ -171,9 +176,23 @@ class LWIPRawListenImpl : public LWIPRawCommon { err_t accept_fn_(struct tcp_pcb *newpcb, err_t err); static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err); + // Accept queue - holds incoming connections briefly until the event loop calls accept() + // This is NOT a connection pool - just a temporary queue between LWIP callbacks and the main loop + // 3 slots is plenty since connections are pulled out quickly by the event loop + // + // Memory analysis: std::array<3> vs original std::queue implementation: + // - std::queue uses std::deque internally which on 32-bit systems needs: + // 24 bytes (deque object) + 32+ bytes (map array) + heap allocations + // Total: ~56+ bytes minimum, plus heap fragmentation + // - std::array<3>: 12 bytes fixed (3 pointers × 4 bytes) + // Saves ~44+ bytes RAM per listening socket + avoids ALL heap allocations + // Used on ESP8266 and RP2040 (platforms using LWIP_TCP implementation) + // + // By using a separate listening socket class, regular connected sockets save + // 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) of memory overhead on 32-bit systems static constexpr size_t MAX_ACCEPTED_SOCKETS = 3; std::array, MAX_ACCEPTED_SOCKETS> accepted_sockets_; - uint8_t accepted_socket_count_ = 0; + uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue }; } // namespace esphome::socket