From 09fc3bb9f5b4d96a29b072383e3cf11582e29823 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 19:22:09 -1000 Subject: [PATCH] [wifi] Replace FreeRTOS queue with LockFreeQueue on ESP-IDF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the FreeRTOS xQueue (xQueueCreate/xQueueSend/xQueueReceive) used for WiFi event passing with a lock-free SPSC queue. This avoids the FreeRTOS kernel spinlock overhead on every loop iteration when checking for events — the common case is an empty queue. The LockFreeQueue::pop() fast path is just two atomic loads and a comparison, vs xQueueReceive which takes a spinlock, checks the queue, and releases the spinlock even when empty. WiFi events are rare (connect/disconnect/scan) so heap allocation for event data is retained — no EventPool needed unlike the BLE path which processes hundreds of events per second. This matches the lock-free pattern already used by esp32_ble. --- .../wifi/wifi_component_esp_idf.cpp | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d8b3db9667..9bf8192ee1 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -39,6 +39,7 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/lock_free_queue.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -47,8 +48,11 @@ namespace esphome::wifi { static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Lock-free SPSC queue replaces FreeRTOS xQueue to avoid kernel spinlock +// overhead on every loop iteration. WiFi events are rare so 64 slots is plenty. +// 65 slots = 64 usable (ring buffer reserves one slot to distinguish full from empty) +static LockFreeQueue s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #ifdef USE_WIFI_AP static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #endif // USE_WIFI_AP @@ -132,11 +136,10 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi return; } - // copy to heap to keep queue object small + // copy to heap — WiFi events are rare so heap alloc is fine auto *to_send = new IDFWiFiEvent; // NOLINT(cppcoreguidelines-owning-memory) memcpy(to_send, &event, sizeof(IDFWiFiEvent)); - // don't block, we may miss events but the core can handle that - if (xQueueSend(s_event_queue, &to_send, 0L) != pdPASS) { + if (!s_event_queue.push(to_send)) { delete to_send; // NOLINT(cppcoreguidelines-owning-memory) } } @@ -157,12 +160,6 @@ void WiFiComponent::wifi_pre_setup_() { ESP_LOGE(TAG, "xEventGroupCreate failed"); return; } - // NOLINTNEXTLINE(bugprone-sizeof-expression) - s_event_queue = xQueueCreate(64, sizeof(IDFWiFiEvent *)); - if (s_event_queue == nullptr) { - ESP_LOGE(TAG, "xQueueCreate failed"); - return; - } err = esp_event_loop_create_default(); if (err != ERR_OK) { ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err)); @@ -724,16 +721,9 @@ const char *get_disconnect_reason_str(uint8_t reason) { } void WiFiComponent::wifi_loop_() { - while (true) { - IDFWiFiEvent *data; - if (xQueueReceive(s_event_queue, &data, 0L) != pdTRUE) { - // no event ready - break; - } - - // process event + IDFWiFiEvent *data; + while ((data = s_event_queue.pop()) != nullptr) { wifi_process_event_(data); - delete data; // NOLINT(cppcoreguidelines-owning-memory) } }