diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 1db905daea..2d4e0ea7c2 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -34,11 +34,10 @@ BSDSocketImpl::~BSDSocketImpl() { int BSDSocketImpl::close() { if (!this->closed_) { -#ifdef USE_LWIP_FAST_SELECT - // All LwIP sockets share the same static event_callback, so there is no per-socket - // unhook needed — just drop the cached pointer before the socket is destroyed. - this->cached_sock_ = nullptr; -#else +#ifndef USE_LWIP_FAST_SELECT + // All LwIP sockets share the same static event_callback, so on the fast-select path + // there is no per-socket unhook needed. cached_sock_ is not cleared because closed_ + // makes the socket a corpse — no ready() or other member access is valid afterwards. if (this->loop_monitored_) { App.unregister_socket_fd(this->fd_); } diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index e628a22d8d..c65d3a227d 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -34,11 +34,10 @@ LwIPSocketImpl::~LwIPSocketImpl() { int LwIPSocketImpl::close() { if (!this->closed_) { -#ifdef USE_LWIP_FAST_SELECT - // All LwIP sockets share the same static event_callback, so there is no per-socket - // unhook needed — just drop the cached pointer before the socket is destroyed. - this->cached_sock_ = nullptr; -#else +#ifndef USE_LWIP_FAST_SELECT + // All LwIP sockets share the same static event_callback, so on the fast-select path + // there is no per-socket unhook needed. cached_sock_ is not cleared because closed_ + // makes the socket a corpse — no ready() or other member access is valid afterwards. if (this->loop_monitored_) { App.unregister_socket_fd(this->fd_); } diff --git a/esphome/core/application.h b/esphome/core/application.h index 122f9b39c7..47976fd57b 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -895,8 +895,8 @@ inline void ESPHOME_ALWAYS_INLINE Application::yield_with_select_(uint32_t delay // event_callback wrapper (see lwip_fast_select.c) are the single source of truth for // socket wake-ups. Every NETCONN_EVT_RCVPLUS posts an xTaskNotifyGive, so any notification // that lands between wakes keeps the counter non-zero (next ulTaskNotifyTake returns - // immediately) or wakes a blocked Take directly. Also woken by wake_loop_threadsafe() - // from background tasks, or timeout. + // immediately) or wakes a blocked Take directly. Additional wake sources: + // wake_loop_threadsafe() from background tasks, and the delay_ms timeout. if (delay_ms == 0) [[unlikely]] { yield(); return;