diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 4097df80af..23dd81c866 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -716,6 +716,11 @@ const char *get_disconnect_reason_str(uint8_t reason) { } void WiFiComponent::wifi_loop_() { + // Fast path: relaxed check avoids acquire fences (4x memw on Xtensa) when queue is empty. + // Safe from consumer side — worst case we process events one loop iteration late. + if (this->event_queue_.empty_relaxed()) + return; + uint16_t dropped = this->event_queue_.get_and_reset_dropped_count(); if (dropped > 0) { ESP_LOGW(TAG, "Dropped %u WiFi events due to buffer overflow", dropped); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 479030d442..1adfa57332 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -778,7 +778,12 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask( network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } void WiFiComponent::wifi_loop_() { - // Check for dropped events due to queue overflow + // Fast path: skip queue drain when empty. + // On LockFreeQueue platforms, relaxed loads avoid memory fences. + // On FreeRTOSQueue platforms, this is a lightweight uxQueueMessagesWaiting check. + if (this->event_queue_.empty_relaxed()) + return; + uint16_t dropped = this->event_queue_.get_and_reset_dropped_count(); if (dropped > 0) { ESP_LOGW(TAG, "Dropped %" PRIu16 " WiFi events due to buffer overflow", dropped); diff --git a/esphome/core/freertos_queue.h b/esphome/core/freertos_queue.h index 2f3faf818a..a67718ea12 100644 --- a/esphome/core/freertos_queue.h +++ b/esphome/core/freertos_queue.h @@ -82,6 +82,10 @@ template class FreeRTOSQueue { bool empty() const { return uxQueueMessagesWaiting(this->handle_) == 0; } + /// Fast empty check — same as empty() for FreeRTOS queues since + /// uxQueueMessagesWaiting is already a lightweight read. + bool empty_relaxed() const { return this->empty(); } + bool full() const { return uxQueueSpacesAvailable(this->handle_) == 0; } size_t size() const { return uxQueueMessagesWaiting(this->handle_); } diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index 316186ea54..62c3b21fae 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -118,6 +118,12 @@ template class LockFreeQueue { bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); } + /// Fast empty check using relaxed loads — no memory fences on Xtensa. + /// Safe when called only from the consumer side: head_ is only written by the + /// consumer, and tail_ can only advance. Worst case we miss a just-pushed item + /// and catch it next loop iteration. + bool empty_relaxed() const { return head_.load(std::memory_order_relaxed) == tail_.load(std::memory_order_relaxed); } + bool full() const { uint8_t next_tail = next_index(tail_.load(std::memory_order_relaxed)); return next_tail == head_.load(std::memory_order_acquire);