mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'retry_string_cleanup' into integration
This commit is contained in:
		| @@ -83,6 +83,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|   item->type = type; | ||||
|   item->callback = std::move(func); | ||||
|   item->remove = false; | ||||
|   item->is_retry = is_retry; | ||||
|  | ||||
| #ifndef ESPHOME_THREAD_SINGLE | ||||
|   // Special handling for defer() (delay = 0, type = TIMEOUT) | ||||
| @@ -134,8 +135,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|  | ||||
|   // For retries, check if there's a cancelled timeout first | ||||
|   if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && | ||||
|       (has_cancelled_timeout_in_container_(this->items_, component, name_cstr) || | ||||
|        has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr))) { | ||||
|       (has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) || | ||||
|        has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { | ||||
|     // Skip scheduling - the retry was cancelled | ||||
| #ifdef ESPHOME_DEBUG_SCHEDULER | ||||
|     ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); | ||||
| @@ -198,25 +199,27 @@ void retry_handler(const std::shared_ptr<RetryArgs> &args) { | ||||
|   // second execution of `func` happens after `initial_wait_time` | ||||
|   args->scheduler->set_timer_common_( | ||||
|       args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval, | ||||
|       [args]() { retry_handler(args); }, true); | ||||
|       [args]() { retry_handler(args); }, /* is_retry= */ true); | ||||
|   // backoff_increase_factor applied to third & later executions | ||||
|   args->current_interval *= args->backoff_increase_factor; | ||||
| } | ||||
|  | ||||
| void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, | ||||
|                               uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, | ||||
|                               float backoff_increase_factor) { | ||||
|   if (!name.empty()) | ||||
|     this->cancel_retry(component, name); | ||||
| void HOT Scheduler::set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, | ||||
|                                       uint32_t initial_wait_time, uint8_t max_attempts, | ||||
|                                       std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) { | ||||
|   const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); | ||||
|  | ||||
|   if (name_cstr != nullptr) | ||||
|     this->cancel_retry(component, name_cstr); | ||||
|  | ||||
|   if (initial_wait_time == SCHEDULER_DONT_RUN) | ||||
|     return; | ||||
|  | ||||
|   ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", | ||||
|             name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor); | ||||
|             name_cstr ? name_cstr : "", initial_wait_time, max_attempts, backoff_increase_factor); | ||||
|  | ||||
|   if (backoff_increase_factor < 0.0001) { | ||||
|     ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str()); | ||||
|     ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name_cstr ? name_cstr : ""); | ||||
|     backoff_increase_factor = 1; | ||||
|   } | ||||
|  | ||||
| @@ -225,15 +228,36 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin | ||||
|   args->retry_countdown = max_attempts; | ||||
|   args->current_interval = initial_wait_time; | ||||
|   args->component = component; | ||||
|   args->name = "retry$" + name; | ||||
|   args->name = name_cstr ? name_cstr : "";  // Convert to std::string for RetryArgs | ||||
|   args->backoff_increase_factor = backoff_increase_factor; | ||||
|   args->scheduler = this; | ||||
|  | ||||
|   // First execution of `func` immediately | ||||
|   this->set_timeout(component, args->name, 0, [args]() { retry_handler(args); }); | ||||
|   // First execution of `func` immediately - use set_timer_common_ with is_retry=true | ||||
|   this->set_timer_common_( | ||||
|       component, SchedulerItem::TIMEOUT, false, &args->name, 0, [args]() { retry_handler(args); }, | ||||
|       /* is_retry= */ true); | ||||
| } | ||||
|  | ||||
| void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, | ||||
|                               uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, | ||||
|                               float backoff_increase_factor) { | ||||
|   this->set_retry_common_(component, false, &name, initial_wait_time, max_attempts, std::move(func), | ||||
|                           backoff_increase_factor); | ||||
| } | ||||
|  | ||||
| void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, | ||||
|                               std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) { | ||||
|   this->set_retry_common_(component, true, name, initial_wait_time, max_attempts, std::move(func), | ||||
|                           backoff_increase_factor); | ||||
| } | ||||
| bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { | ||||
|   return this->cancel_timeout(component, "retry$" + name); | ||||
|   return this->cancel_retry(component, name.c_str()); | ||||
| } | ||||
|  | ||||
| bool HOT Scheduler::cancel_retry(Component *component, const char *name) { | ||||
|   // Cancel timeouts that have is_retry flag set | ||||
|   LockGuard guard{this->lock_}; | ||||
|   return this->cancel_item_locked_(component, name, SchedulerItem::TIMEOUT, /* match_retry= */ true); | ||||
| } | ||||
|  | ||||
| optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) { | ||||
| @@ -479,7 +503,8 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co | ||||
| } | ||||
|  | ||||
| // Helper to cancel items by name - must be called with lock held | ||||
| bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) { | ||||
| bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type, | ||||
|                                         bool match_retry) { | ||||
|   // Early return if name is invalid - no items to cancel | ||||
|   if (name_cstr == nullptr) { | ||||
|     return false; | ||||
| @@ -492,7 +517,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c | ||||
|   // Only check defer queue for timeouts (intervals never go there) | ||||
|   if (type == SchedulerItem::TIMEOUT) { | ||||
|     for (auto &item : this->defer_queue_) { | ||||
|       if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|       if (this->matches_item_(item, component, name_cstr, type, match_retry)) { | ||||
|         item->remove = true; | ||||
|         total_cancelled++; | ||||
|       } | ||||
| @@ -502,7 +527,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c | ||||
|  | ||||
|   // Cancel items in the main heap | ||||
|   for (auto &item : this->items_) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type, match_retry)) { | ||||
|       item->remove = true; | ||||
|       total_cancelled++; | ||||
|       this->to_remove_++;  // Track removals for heap items | ||||
| @@ -511,7 +536,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c | ||||
|  | ||||
|   // Cancel items in to_add_ | ||||
|   for (auto &item : this->to_add_) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type)) { | ||||
|     if (this->matches_item_(item, component, name_cstr, type, match_retry)) { | ||||
|       item->remove = true; | ||||
|       total_cancelled++; | ||||
|       // Don't track removals for to_add_ items | ||||
|   | ||||
| @@ -61,7 +61,10 @@ class Scheduler { | ||||
|   bool cancel_interval(Component *component, const char *name); | ||||
|   void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, | ||||
|                  std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f); | ||||
|   void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, | ||||
|                  std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f); | ||||
|   bool cancel_retry(Component *component, const std::string &name); | ||||
|   bool cancel_retry(Component *component, const char *name); | ||||
|  | ||||
|   // Calculate when the next scheduled item should run | ||||
|   // @param now Fresh timestamp from millis() - must not be stale/cached | ||||
| @@ -98,11 +101,18 @@ class Scheduler { | ||||
|     enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; | ||||
|     bool remove : 1; | ||||
|     bool name_is_dynamic : 1;  // True if name was dynamically allocated (needs delete[]) | ||||
|     // 5 bits padding | ||||
|     bool is_retry : 1;         // True if this is a retry timeout | ||||
|     // 4 bits padding | ||||
|  | ||||
|     // Constructor | ||||
|     SchedulerItem() | ||||
|         : component(nullptr), interval(0), next_execution_(0), type(TIMEOUT), remove(false), name_is_dynamic(false) { | ||||
|         : component(nullptr), | ||||
|           interval(0), | ||||
|           next_execution_(0), | ||||
|           type(TIMEOUT), | ||||
|           remove(false), | ||||
|           name_is_dynamic(false), | ||||
|           is_retry(false) { | ||||
|       name_.static_name = nullptr; | ||||
|     } | ||||
|  | ||||
| @@ -156,6 +166,10 @@ class Scheduler { | ||||
|   void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, | ||||
|                          uint32_t delay, std::function<void()> func, bool is_retry = false); | ||||
|  | ||||
|   // Common implementation for retry | ||||
|   void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time, | ||||
|                          uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, float backoff_increase_factor); | ||||
|  | ||||
|   uint64_t millis_64_(uint32_t now); | ||||
|   // Cleanup logically deleted items from the scheduler | ||||
|   // Returns the number of items remaining after cleanup | ||||
| @@ -165,7 +179,7 @@ class Scheduler { | ||||
|  | ||||
|  private: | ||||
|   // Helper to cancel items by name - must be called with lock held | ||||
|   bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type); | ||||
|   bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false); | ||||
|  | ||||
|   // Helper to extract name as const char* from either static string or std::string | ||||
|   inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) { | ||||
| @@ -177,8 +191,9 @@ class Scheduler { | ||||
|  | ||||
|   // Helper function to check if item matches criteria for cancellation | ||||
|   inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr, | ||||
|                                 SchedulerItem::Type type, bool skip_removed = true) const { | ||||
|     if (item->component != component || item->type != type || (skip_removed && item->remove)) { | ||||
|                                 SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { | ||||
|     if (item->component != component || item->type != type || (skip_removed && item->remove) || | ||||
|         (match_retry && !item->is_retry)) { | ||||
|       return false; | ||||
|     } | ||||
|     const char *item_name = item->get_name(); | ||||
| @@ -206,10 +221,11 @@ class Scheduler { | ||||
|  | ||||
|   // Template helper to check if any item in a container matches our criteria | ||||
|   template<typename Container> | ||||
|   bool has_cancelled_timeout_in_container_(const Container &container, Component *component, | ||||
|                                            const char *name_cstr) const { | ||||
|   bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr, | ||||
|                                            bool match_retry) const { | ||||
|     for (const auto &item : container) { | ||||
|       if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, false)) { | ||||
|       if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, | ||||
|                                               /* skip_removed= */ false)) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user