mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'reduce_main_loop' into integration
This commit is contained in:
		| @@ -255,11 +255,7 @@ void DeferredUpdateEventSourceList::on_client_disconnect_(DeferredUpdateEventSou | ||||
| } | ||||
| #endif | ||||
|  | ||||
| WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) { | ||||
| #ifdef USE_ESP32 | ||||
|   to_schedule_lock_ = xSemaphoreCreateMutex(); | ||||
| #endif | ||||
| } | ||||
| WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {} | ||||
|  | ||||
| #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||
| void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } | ||||
| @@ -308,30 +304,7 @@ void WebServer::setup() { | ||||
|   // getting a lot of events | ||||
|   this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); }); | ||||
| } | ||||
| void WebServer::loop() { | ||||
| #ifdef USE_ESP32 | ||||
|   // Check atomic flag first to avoid taking semaphore when queue is empty | ||||
|   if (this->to_schedule_has_items_.load(std::memory_order_relaxed) && xSemaphoreTake(this->to_schedule_lock_, 0L)) { | ||||
|     std::function<void()> fn; | ||||
|     if (!to_schedule_.empty()) { | ||||
|       // scheduler execute things out of order which may lead to incorrect state | ||||
|       // this->defer(std::move(to_schedule_.front())); | ||||
|       // let's execute it directly from the loop | ||||
|       fn = std::move(to_schedule_.front()); | ||||
|       to_schedule_.pop_front(); | ||||
|       if (to_schedule_.empty()) { | ||||
|         this->to_schedule_has_items_.store(false, std::memory_order_relaxed); | ||||
|       } | ||||
|     } | ||||
|     xSemaphoreGive(this->to_schedule_lock_); | ||||
|     if (fn) { | ||||
|       fn(); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   this->events_.loop(); | ||||
| } | ||||
| void WebServer::loop() { this->events_.loop(); } | ||||
| void WebServer::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "Web Server:\n" | ||||
| @@ -526,13 +499,13 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM | ||||
|       std::string data = this->switch_json(obj, obj->state, detail); | ||||
|       request->send(200, "application/json", data.c_str()); | ||||
|     } else if (match.method_equals("toggle")) { | ||||
|       this->schedule_([obj]() { obj->toggle(); }); | ||||
|       this->defer([obj]() { obj->toggle(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("turn_on")) { | ||||
|       this->schedule_([obj]() { obj->turn_on(); }); | ||||
|       this->defer([obj]() { obj->turn_on(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("turn_off")) { | ||||
|       this->schedule_([obj]() { obj->turn_off(); }); | ||||
|       this->defer([obj]() { obj->turn_off(); }); | ||||
|       request->send(200); | ||||
|     } else { | ||||
|       request->send(404); | ||||
| @@ -568,7 +541,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM | ||||
|       std::string data = this->button_json(obj, detail); | ||||
|       request->send(200, "application/json", data.c_str()); | ||||
|     } else if (match.method_equals("press")) { | ||||
|       this->schedule_([obj]() { obj->press(); }); | ||||
|       this->defer([obj]() { obj->press(); }); | ||||
|       request->send(200); | ||||
|       return; | ||||
|     } else { | ||||
| @@ -648,7 +621,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc | ||||
|       std::string data = this->fan_json(obj, detail); | ||||
|       request->send(200, "application/json", data.c_str()); | ||||
|     } else if (match.method_equals("toggle")) { | ||||
|       this->schedule_([obj]() { obj->toggle().perform(); }); | ||||
|       this->defer([obj]() { obj->toggle().perform(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("turn_on") || match.method_equals("turn_off")) { | ||||
|       auto call = match.method_equals("turn_on") ? obj->turn_on() : obj->turn_off(); | ||||
| @@ -680,7 +653,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc | ||||
|             return; | ||||
|         } | ||||
|       } | ||||
|       this->schedule_([call]() mutable { call.perform(); }); | ||||
|       this->defer([call]() mutable { call.perform(); }); | ||||
|       request->send(200); | ||||
|     } else { | ||||
|       request->send(404); | ||||
| @@ -729,7 +702,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa | ||||
|       std::string data = this->light_json(obj, detail); | ||||
|       request->send(200, "application/json", data.c_str()); | ||||
|     } else if (match.method_equals("toggle")) { | ||||
|       this->schedule_([obj]() { obj->toggle().perform(); }); | ||||
|       this->defer([obj]() { obj->toggle().perform(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("turn_on")) { | ||||
|       auto call = obj->turn_on(); | ||||
| @@ -786,7 +759,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa | ||||
|         call.set_effect(effect); | ||||
|       } | ||||
|  | ||||
|       this->schedule_([call]() mutable { call.perform(); }); | ||||
|       this->defer([call]() mutable { call.perform(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("turn_off")) { | ||||
|       auto call = obj->turn_off(); | ||||
| @@ -796,7 +769,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa | ||||
|           call.set_transition_length(*transition * 1000); | ||||
|         } | ||||
|       } | ||||
|       this->schedule_([call]() mutable { call.perform(); }); | ||||
|       this->defer([call]() mutable { call.perform(); }); | ||||
|       request->send(200); | ||||
|     } else { | ||||
|       request->send(404); | ||||
| @@ -881,7 +854,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -939,7 +912,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM | ||||
|         call.set_value(*value); | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1014,7 +987,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat | ||||
|       call.set_date(value); | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1073,7 +1046,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat | ||||
|       call.set_time(value); | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1131,7 +1104,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur | ||||
|       call.set_datetime(value); | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1248,7 +1221,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM | ||||
|       call.set_option(option.c_str());  // NOLINT | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1335,7 +1308,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url | ||||
|         call.set_target_temperature(*target_temperature); | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1452,13 +1425,13 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat | ||||
|       std::string data = this->lock_json(obj, obj->state, detail); | ||||
|       request->send(200, "application/json", data.c_str()); | ||||
|     } else if (match.method_equals("lock")) { | ||||
|       this->schedule_([obj]() { obj->lock(); }); | ||||
|       this->defer([obj]() { obj->lock(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("unlock")) { | ||||
|       this->schedule_([obj]() { obj->unlock(); }); | ||||
|       this->defer([obj]() { obj->unlock(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method_equals("open")) { | ||||
|       this->schedule_([obj]() { obj->open(); }); | ||||
|       this->defer([obj]() { obj->open(); }); | ||||
|       request->send(200); | ||||
|     } else { | ||||
|       request->send(404); | ||||
| @@ -1529,7 +1502,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1594,7 +1567,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this->schedule_([call]() mutable { call.perform(); }); | ||||
|     this->defer([call]() mutable { call.perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -1695,7 +1668,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this->schedule_([obj]() mutable { obj->perform(); }); | ||||
|     this->defer([obj]() mutable { obj->perform(); }); | ||||
|     request->send(200); | ||||
|     return; | ||||
|   } | ||||
| @@ -2072,17 +2045,6 @@ void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_na | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void WebServer::schedule_(std::function<void()> &&f) { | ||||
| #ifdef USE_ESP32 | ||||
|   xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY); | ||||
|   to_schedule_.push_back(std::move(f)); | ||||
|   this->to_schedule_has_items_.store(true, std::memory_order_relaxed); | ||||
|   xSemaphoreGive(this->to_schedule_lock_); | ||||
| #else | ||||
|   this->defer(std::move(f)); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| }  // namespace web_server | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -14,12 +14,6 @@ | ||||
| #include <string> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #ifdef USE_ESP32 | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/semphr.h> | ||||
| #include <deque> | ||||
| #include <atomic> | ||||
| #endif | ||||
|  | ||||
| #if USE_WEBSERVER_VERSION >= 2 | ||||
| extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM; | ||||
| @@ -504,7 +498,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|  | ||||
|  protected: | ||||
|   void add_sorting_info_(JsonObject &root, EntityBase *entity); | ||||
|   void schedule_(std::function<void()> &&f); | ||||
|   web_server_base::WebServerBase *base_; | ||||
| #ifdef USE_ARDUINO | ||||
|   DeferredUpdateEventSourceList events_; | ||||
| @@ -524,11 +517,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|   const char *js_include_{nullptr}; | ||||
| #endif | ||||
|   bool expose_log_{true}; | ||||
| #ifdef USE_ESP32 | ||||
|   std::deque<std::function<void()>> to_schedule_; | ||||
|   SemaphoreHandle_t to_schedule_lock_; | ||||
|   std::atomic<bool> to_schedule_has_items_{false}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace web_server | ||||
|   | ||||
| @@ -73,8 +73,6 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|   if (delay == SCHEDULER_DONT_RUN) | ||||
|     return; | ||||
|  | ||||
|   const auto now = this->millis_(); | ||||
|  | ||||
|   // Create and populate the scheduler item | ||||
|   auto item = make_unique<SchedulerItem>(); | ||||
|   item->component = component; | ||||
| @@ -83,6 +81,16 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|   item->callback = std::move(func); | ||||
|   item->remove = false; | ||||
|  | ||||
|   // Special handling for defer() (delay = 0, type = TIMEOUT) | ||||
|   if (delay == 0 && type == SchedulerItem::TIMEOUT) { | ||||
|     // Put in defer queue for guaranteed FIFO execution | ||||
|     LockGuard guard{this->lock_}; | ||||
|     this->defer_queue_.push_back(std::move(item)); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const auto now = this->millis_(); | ||||
|  | ||||
|   // Type-specific setup | ||||
|   if (type == SchedulerItem::INTERVAL) { | ||||
|     item->interval = delay; | ||||
| @@ -209,6 +217,28 @@ optional<uint32_t> HOT Scheduler::next_schedule_in() { | ||||
|   return item->next_execution_ - now; | ||||
| } | ||||
| void HOT Scheduler::call() { | ||||
|   // Process defer queue first to guarantee FIFO execution order for deferred items. | ||||
|   // Previously, defer() used the heap which gave undefined order for equal timestamps, | ||||
|   // causing race conditions on multi-core systems (ESP32, RP2040, BK7200). | ||||
|   // With the defer queue: | ||||
|   // - Deferred items (delay=0) go directly to defer_queue_ in set_timer_common_ | ||||
|   // - Items execute in exact order they were deferred (FIFO guarantee) | ||||
|   // - No deferred items exist in to_add_, so processing order doesn't affect correctness | ||||
|   while (!this->defer_queue_.empty()) { | ||||
|     std::unique_ptr<SchedulerItem> item; | ||||
|     { | ||||
|       LockGuard guard{this->lock_}; | ||||
|       if (this->defer_queue_.empty())  // Double-check with lock held | ||||
|         break; | ||||
|       item = std::move(this->defer_queue_.front()); | ||||
|       this->defer_queue_.pop_front(); | ||||
|     } | ||||
|     // Skip if item was marked for removal or component failed | ||||
|     if (!this->should_skip_item_(item.get())) { | ||||
|       this->execute_item_(item.get()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const auto now = this->millis_(); | ||||
|   this->process_to_add(); | ||||
|  | ||||
| @@ -294,13 +324,7 @@ void HOT Scheduler::call() { | ||||
|       // Warning: During callback(), a lot of stuff can happen, including: | ||||
|       //  - timeouts/intervals get added, potentially invalidating vector pointers | ||||
|       //  - timeouts/intervals get cancelled | ||||
|       { | ||||
|         uint32_t now_ms = millis(); | ||||
|         WarnIfComponentBlockingGuard guard{item->component, now_ms}; | ||||
|         item->callback(); | ||||
|         // Call finish to ensure blocking time is properly calculated and reported | ||||
|         guard.finish(); | ||||
|       } | ||||
|       this->execute_item_(item.get()); | ||||
|     } | ||||
|  | ||||
|     { | ||||
| @@ -364,6 +388,26 @@ void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) { | ||||
|   LockGuard guard{this->lock_}; | ||||
|   this->to_add_.push_back(std::move(item)); | ||||
| } | ||||
| // Helper function to check if item matches criteria for cancellation | ||||
| bool HOT Scheduler::matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, | ||||
|                                   const char *name_cstr, SchedulerItem::Type type) { | ||||
|   if (item->component != component || item->type != type || item->remove) { | ||||
|     return false; | ||||
|   } | ||||
|   const char *item_name = item->get_name(); | ||||
|   return item_name != nullptr && strcmp(name_cstr, item_name) == 0; | ||||
| } | ||||
|  | ||||
| // Helper to execute a scheduler item | ||||
| void HOT Scheduler::execute_item_(SchedulerItem *item) { | ||||
|   App.set_current_component(item->component); | ||||
|  | ||||
|   uint32_t now_ms = millis(); | ||||
|   WarnIfComponentBlockingGuard guard{item->component, now_ms}; | ||||
|   item->callback(); | ||||
|   guard.finish(); | ||||
| } | ||||
|  | ||||
| // Common implementation for cancel operations | ||||
| bool HOT Scheduler::cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, | ||||
|                                         SchedulerItem::Type type) { | ||||
| @@ -379,19 +423,25 @@ bool HOT Scheduler::cancel_item_common_(Component *component, bool is_static_str | ||||
|   LockGuard guard{this->lock_}; | ||||
|   bool ret = false; | ||||
|  | ||||
|   for (auto &it : this->items_) { | ||||
|     const char *item_name = it->get_name(); | ||||
|     if (it->component == component && item_name != nullptr && strcmp(name_cstr, item_name) == 0 && it->type == type && | ||||
|         !it->remove) { | ||||
|       to_remove_++; | ||||
|       it->remove = true; | ||||
|   // Check all containers for matching items | ||||
|   for (auto &item : this->defer_queue_) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|       item->remove = true; | ||||
|       ret = true; | ||||
|     } | ||||
|   } | ||||
|   for (auto &it : this->to_add_) { | ||||
|     const char *item_name = it->get_name(); | ||||
|     if (it->component == component && item_name != nullptr && strcmp(name_cstr, item_name) == 0 && it->type == type) { | ||||
|       it->remove = true; | ||||
|  | ||||
|   for (auto &item : this->items_) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|       item->remove = true; | ||||
|       ret = true; | ||||
|       this->to_remove_++;  // Only track removals for heap items | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   for (auto &item : this->to_add_) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|       item->remove = true; | ||||
|       ret = true; | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include <vector> | ||||
| #include <memory> | ||||
| #include <deque> | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| @@ -145,6 +146,18 @@ class Scheduler { | ||||
|   bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type); | ||||
|   bool cancel_item_(Component *component, const char *name, SchedulerItem::Type type); | ||||
|  | ||||
|   // Helper functions for cancel operations | ||||
|   bool matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr, | ||||
|                      SchedulerItem::Type type); | ||||
|  | ||||
|   // Helper to execute a scheduler item | ||||
|   void execute_item_(SchedulerItem *item); | ||||
|  | ||||
|   // Helper to check if item should be skipped | ||||
|   bool should_skip_item_(const SchedulerItem *item) const { | ||||
|     return item->remove || (item->component != nullptr && item->component->is_failed()); | ||||
|   } | ||||
|  | ||||
|   bool empty_() { | ||||
|     this->cleanup_(); | ||||
|     return this->items_.empty(); | ||||
| @@ -153,6 +166,7 @@ class Scheduler { | ||||
|   Mutex lock_; | ||||
|   std::vector<std::unique_ptr<SchedulerItem>> items_; | ||||
|   std::vector<std::unique_ptr<SchedulerItem>> to_add_; | ||||
|   std::deque<std::unique_ptr<SchedulerItem>> defer_queue_;  // FIFO queue for defer() calls | ||||
|   uint32_t last_millis_{0}; | ||||
|   uint16_t millis_major_{0}; | ||||
|   uint32_t to_remove_{0}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user