[mdns] Throttle MDNS.update() polling on ESP8266 and RP2040

On ESP8266 and RP2040, MDNS.update() is called every loop iteration
(~120 Hz) but only manages timer-driven probe/announce state machines.
Incoming mDNS packets are handled independently via the lwIP onRx UDP
callback and are unaffected by update() frequency.

The shortest internal timer is the 250ms probe interval (RFC 6762).
Throttling to 50ms provides sufficient resolution while reducing CPU
overhead by ~84% (from ~360ms to ~60ms per 60s measurement period).
This commit is contained in:
J. Nick Koston
2026-02-10 14:28:26 -06:00
parent d152438335
commit 1b13f49e1b
3 changed files with 44 additions and 2 deletions

View File

@@ -48,6 +48,25 @@ class MDNSComponent : public Component {
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
void loop() override;
#endif
// Throttle interval for MDNS.update() on platforms that require polling (ESP8266, RP2040).
//
// On these platforms, MDNS.update() calls _process(true) which only manages timer-driven
// state machines (probe/announce timeouts and service query cache TTLs). Incoming mDNS
// packets are handled independently via the lwIP onRx UDP callback and are NOT affected
// by how often update() is called.
//
// The shortest internal timer is the 250ms probe interval (RFC 6762 Section 8.1).
// Announcement intervals are 1000ms and cache TTL checks are on the order of seconds
// to minutes. A 50ms polling interval provides sufficient resolution for all timers
// while reducing CPU overhead by ~84% compared to calling every loop iteration.
//
// In steady state (after the ~8 second boot probe/announce phase completes), update()
// checks timers that are set to never expire, making every call pure overhead.
//
// Tasmota uses a 50ms main loop cycle with mDNS working correctly, confirming this
// interval is safe in production.
static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50;
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
#ifdef USE_MDNS_EXTRA_SERVICES
@@ -110,6 +129,9 @@ class MDNSComponent : public Component {
#endif
#ifdef USE_MDNS_STORE_SERVICES
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
#endif
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
uint32_t last_update_{0};
#endif
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, char *mac_address_buf);
};

View File

@@ -38,7 +38,17 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp8266); }
void MDNSComponent::loop() { MDNS.update(); }
void MDNSComponent::loop() {
// Throttle MDNS.update() to avoid calling it every loop iteration (~120 Hz).
// The update() function only manages timer-driven probe/announce state machines
// and service query cache TTLs. Incoming mDNS packets are processed independently
// via the lwIP onRx callback and do not depend on update() frequency.
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_update_ < MDNS_UPDATE_INTERVAL_MS)
return;
this->last_update_ = now;
MDNS.update();
}
void MDNSComponent::on_shutdown() {
MDNS.close();

View File

@@ -37,7 +37,17 @@ static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERV
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_rp2040); }
void MDNSComponent::loop() { MDNS.update(); }
void MDNSComponent::loop() {
// Throttle MDNS.update() to avoid calling it every loop iteration (~120 Hz).
// The update() function only manages timer-driven probe/announce state machines
// and service query cache TTLs. Incoming mDNS packets are processed independently
// via the lwIP onRx callback and do not depend on update() frequency.
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_update_ < MDNS_UPDATE_INTERVAL_MS)
return;
this->last_update_ = now;
MDNS.update();
}
void MDNSComponent::on_shutdown() {
MDNS.close();