mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	Make retry scheduler efficient (#3225)
This commit is contained in:
		| @@ -61,7 +61,7 @@ extern const uint32_t STATUS_LED_OK; | |||||||
| extern const uint32_t STATUS_LED_WARNING; | extern const uint32_t STATUS_LED_WARNING; | ||||||
| extern const uint32_t STATUS_LED_ERROR; | extern const uint32_t STATUS_LED_ERROR; | ||||||
|  |  | ||||||
| enum RetryResult { DONE, RETRY }; | enum class RetryResult { DONE, RETRY }; | ||||||
|  |  | ||||||
| class Component { | class Component { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10; | |||||||
| // #define ESPHOME_DEBUG_SCHEDULER | // #define ESPHOME_DEBUG_SCHEDULER | ||||||
|  |  | ||||||
| void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, | void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, | ||||||
|                                 std::function<void()> &&func) { |                                 std::function<void()> func) { | ||||||
|   const uint32_t now = this->millis_(); |   const uint32_t now = this->millis_(); | ||||||
|  |  | ||||||
|   if (!name.empty()) |   if (!name.empty()) | ||||||
| @@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u | |||||||
|   item->timeout = timeout; |   item->timeout = timeout; | ||||||
|   item->last_execution = now; |   item->last_execution = now; | ||||||
|   item->last_execution_major = this->millis_major_; |   item->last_execution_major = this->millis_major_; | ||||||
|   item->void_callback = std::move(func); |   item->callback = std::move(func); | ||||||
|   item->remove = false; |   item->remove = false; | ||||||
|   this->push_(std::move(item)); |   this->push_(std::move(item)); | ||||||
| } | } | ||||||
| @@ -40,7 +40,7 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name | |||||||
|   return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); |   return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); | ||||||
| } | } | ||||||
| void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, | void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, | ||||||
|                                  std::function<void()> &&func) { |                                  std::function<void()> func) { | ||||||
|   const uint32_t now = this->millis_(); |   const uint32_t now = this->millis_(); | ||||||
|  |  | ||||||
|   if (!name.empty()) |   if (!name.empty()) | ||||||
| @@ -65,7 +65,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, | |||||||
|   item->last_execution_major = this->millis_major_; |   item->last_execution_major = this->millis_major_; | ||||||
|   if (item->last_execution > now) |   if (item->last_execution > now) | ||||||
|     item->last_execution_major--; |     item->last_execution_major--; | ||||||
|   item->void_callback = std::move(func); |   item->callback = std::move(func); | ||||||
|   item->remove = false; |   item->remove = false; | ||||||
|   this->push_(std::move(item)); |   this->push_(std::move(item)); | ||||||
| } | } | ||||||
| @@ -73,11 +73,26 @@ bool HOT Scheduler::cancel_interval(Component *component, const std::string &nam | |||||||
|   return this->cancel_item_(component, name, SchedulerItem::INTERVAL); |   return this->cancel_item_(component, name, SchedulerItem::INTERVAL); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, | struct RetryArgs { | ||||||
|                               uint8_t max_attempts, std::function<RetryResult()> &&func, |   std::function<RetryResult()> func; | ||||||
|                               float backoff_increase_factor) { |   uint8_t retry_countdown; | ||||||
|   const uint32_t now = this->millis_(); |   uint32_t current_interval; | ||||||
|  |   Component *component; | ||||||
|  |   std::string name; | ||||||
|  |   float backoff_increase_factor; | ||||||
|  |   Scheduler *scheduler; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static void retry_handler(const std::shared_ptr<RetryArgs> &args) { | ||||||
|  |   RetryResult retry_result = args->func(); | ||||||
|  |   if (retry_result == RetryResult::DONE || --args->retry_countdown <= 0) | ||||||
|  |     return; | ||||||
|  |   args->current_interval *= args->backoff_increase_factor; | ||||||
|  |   args->scheduler->set_timeout(args->component, args->name, args->current_interval, [args]() { retry_handler(args); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, | ||||||
|  |                               uint8_t max_attempts, std::function<RetryResult()> func, float backoff_increase_factor) { | ||||||
|   if (!name.empty()) |   if (!name.empty()) | ||||||
|     this->cancel_retry(component, name); |     this->cancel_retry(component, name); | ||||||
|  |  | ||||||
| @@ -87,23 +102,19 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin | |||||||
|   ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), |   ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), | ||||||
|             initial_wait_time, max_attempts, backoff_increase_factor); |             initial_wait_time, max_attempts, backoff_increase_factor); | ||||||
|  |  | ||||||
|   auto item = make_unique<SchedulerItem>(); |   auto args = std::make_shared<RetryArgs>(); | ||||||
|   item->component = component; |   args->func = std::move(func); | ||||||
|   item->name = name; |   args->retry_countdown = max_attempts; | ||||||
|   item->type = SchedulerItem::RETRY; |   args->current_interval = initial_wait_time; | ||||||
|   item->interval = initial_wait_time; |   args->component = component; | ||||||
|   item->retry_countdown = max_attempts; |   args->name = "retry$" + name; | ||||||
|   item->backoff_multiplier = backoff_increase_factor; |   args->backoff_increase_factor = backoff_increase_factor; | ||||||
|   item->last_execution = now - initial_wait_time; |   args->scheduler = this; | ||||||
|   item->last_execution_major = this->millis_major_; |  | ||||||
|   if (item->last_execution > now) |   this->set_timeout(component, args->name, initial_wait_time, [args]() { retry_handler(args); }); | ||||||
|     item->last_execution_major--; |  | ||||||
|   item->retry_callback = std::move(func); |  | ||||||
|   item->remove = false; |  | ||||||
|   this->push_(std::move(item)); |  | ||||||
| } | } | ||||||
| bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { | bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { | ||||||
|   return this->cancel_item_(component, name, SchedulerItem::RETRY); |   return this->cancel_timeout(component, "retry$" + name); | ||||||
| } | } | ||||||
|  |  | ||||||
| optional<uint32_t> HOT Scheduler::next_schedule_in() { | optional<uint32_t> HOT Scheduler::next_schedule_in() { | ||||||
| @@ -162,7 +173,6 @@ void HOT Scheduler::call() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   while (!this->empty_()) { |   while (!this->empty_()) { | ||||||
|     RetryResult retry_result = RETRY; |  | ||||||
|     // use scoping to indicate visibility of `item` variable |     // use scoping to indicate visibility of `item` variable | ||||||
|     { |     { | ||||||
|       // Don't copy-by value yet |       // Don't copy-by value yet | ||||||
| @@ -191,11 +201,7 @@ void HOT Scheduler::call() { | |||||||
|       //  - timeouts/intervals get cancelled |       //  - timeouts/intervals get cancelled | ||||||
|       { |       { | ||||||
|         WarnIfComponentBlockingGuard guard{item->component}; |         WarnIfComponentBlockingGuard guard{item->component}; | ||||||
|         if (item->type == SchedulerItem::RETRY) { |         item->callback(); | ||||||
|           retry_result = item->retry_callback(); |  | ||||||
|         } else { |  | ||||||
|           item->void_callback(); |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -213,16 +219,13 @@ void HOT Scheduler::call() { | |||||||
|         continue; |         continue; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (item->type == SchedulerItem::INTERVAL || |       if (item->type == SchedulerItem::INTERVAL) { | ||||||
|           (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) { |  | ||||||
|         if (item->interval != 0) { |         if (item->interval != 0) { | ||||||
|           const uint32_t before = item->last_execution; |           const uint32_t before = item->last_execution; | ||||||
|           const uint32_t amount = (now - item->last_execution) / item->interval; |           const uint32_t amount = (now - item->last_execution) / item->interval; | ||||||
|           item->last_execution += amount * item->interval; |           item->last_execution += amount * item->interval; | ||||||
|           if (item->last_execution < before) |           if (item->last_execution < before) | ||||||
|             item->last_execution_major++; |             item->last_execution_major++; | ||||||
|           if (item->type == SchedulerItem::RETRY) |  | ||||||
|             item->interval *= item->backoff_multiplier; |  | ||||||
|         } |         } | ||||||
|         this->push_(std::move(item)); |         this->push_(std::move(item)); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -10,13 +10,13 @@ class Component; | |||||||
|  |  | ||||||
| class Scheduler { | class Scheduler { | ||||||
|  public: |  public: | ||||||
|   void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> &&func); |   void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func); | ||||||
|   bool cancel_timeout(Component *component, const std::string &name); |   bool cancel_timeout(Component *component, const std::string &name); | ||||||
|   void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> &&func); |   void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func); | ||||||
|   bool cancel_interval(Component *component, const std::string &name); |   bool cancel_interval(Component *component, const std::string &name); | ||||||
|  |  | ||||||
|   void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, |   void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, | ||||||
|                  std::function<RetryResult()> &&func, float backoff_increase_factor = 1.0f); |                  std::function<RetryResult()> func, float backoff_increase_factor = 1.0f); | ||||||
|   bool cancel_retry(Component *component, const std::string &name); |   bool cancel_retry(Component *component, const std::string &name); | ||||||
|  |  | ||||||
|   optional<uint32_t> next_schedule_in(); |   optional<uint32_t> next_schedule_in(); | ||||||
| @@ -29,20 +29,13 @@ class Scheduler { | |||||||
|   struct SchedulerItem { |   struct SchedulerItem { | ||||||
|     Component *component; |     Component *component; | ||||||
|     std::string name; |     std::string name; | ||||||
|     enum Type { TIMEOUT, INTERVAL, RETRY } type; |     enum Type { TIMEOUT, INTERVAL } type; | ||||||
|     union { |     union { | ||||||
|       uint32_t interval; |       uint32_t interval; | ||||||
|       uint32_t timeout; |       uint32_t timeout; | ||||||
|     }; |     }; | ||||||
|     uint32_t last_execution; |     uint32_t last_execution; | ||||||
|     // Ideally this should be a union or std::variant |     std::function<void()> callback; | ||||||
|     // but unions don't work with object like std::function |  | ||||||
|     //  union CallBack_{ |  | ||||||
|     std::function<void()> void_callback; |  | ||||||
|     std::function<RetryResult()> retry_callback; |  | ||||||
|     //  }; |  | ||||||
|     uint8_t retry_countdown{3}; |  | ||||||
|     float backoff_multiplier{1.0f}; |  | ||||||
|     bool remove; |     bool remove; | ||||||
|     uint8_t last_execution_major; |     uint8_t last_execution_major; | ||||||
|  |  | ||||||
| @@ -60,8 +53,6 @@ class Scheduler { | |||||||
|       switch (this->type) { |       switch (this->type) { | ||||||
|         case SchedulerItem::INTERVAL: |         case SchedulerItem::INTERVAL: | ||||||
|           return "interval"; |           return "interval"; | ||||||
|         case SchedulerItem::RETRY: |  | ||||||
|           return "retry"; |  | ||||||
|         case SchedulerItem::TIMEOUT: |         case SchedulerItem::TIMEOUT: | ||||||
|           return "timeout"; |           return "timeout"; | ||||||
|         default: |         default: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user