diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index f8ef014b09..cd2ae8ef97 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -87,10 +87,13 @@ void MDNSComponent::start_polling_window_() { // uint32_t-ID set_interval/set_timeout already does atomic cancel-and-add. this->set_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, []() { #ifdef USE_MDNS_WIFI_LISTENER - // Every send fails while the radio is off channel or no interface is up, and each one - // spins sendTimeout() for 50 ms; skip the tick rather than stall the loop for nothing. + // MDNS.update() can suspend the loop in UdpContext::sendTimeout() while a send is + // failing (radio off-channel during a roam scan, or mid reconnect); an incoming + // packet then re-enters LEAmDNS from lwIP and corrupts shared UdpContext state. + // Skip the tick while the radio cannot transmit (#18760), but keep polling while + // the AP is serving clients (AP-only or fallback AP with the STA down). auto *wifi = wifi::global_wifi_component; - if (wifi->is_roaming_scan_active() || (!wifi->is_connected() && !wifi->is_ap_active())) + if (wifi->is_roaming() || (!wifi->is_connected() && !wifi->is_ap_active())) return; #endif mdns_responder.update_guarded();