ESPHome  2022.6.3
scheduler.cpp
Go to the documentation of this file.
1 #include "scheduler.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/hal.h"
5 #include <algorithm>
6 
7 namespace esphome {
8 
9 static const char *const TAG = "scheduler";
10 
11 static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
12 
13 // Uncomment to debug scheduler
14 // #define ESPHOME_DEBUG_SCHEDULER
15 
16 void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
17  std::function<void()> func) {
18  const uint32_t now = this->millis_();
19 
20  if (!name.empty())
21  this->cancel_timeout(component, name);
22 
23  if (timeout == SCHEDULER_DONT_RUN)
24  return;
25 
26  ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%u)", name.c_str(), timeout);
27 
28  auto item = make_unique<SchedulerItem>();
29  item->component = component;
30  item->name = name;
31  item->type = SchedulerItem::TIMEOUT;
32  item->timeout = timeout;
33  item->last_execution = now;
34  item->last_execution_major = this->millis_major_;
35  item->callback = std::move(func);
36  item->remove = false;
37  this->push_(std::move(item));
38 }
39 bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) {
40  return this->cancel_item_(component, name, SchedulerItem::TIMEOUT);
41 }
42 void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
43  std::function<void()> func) {
44  const uint32_t now = this->millis_();
45 
46  if (!name.empty())
47  this->cancel_interval(component, name);
48 
49  if (interval == SCHEDULER_DONT_RUN)
50  return;
51 
52  // only put offset in lower half
53  uint32_t offset = 0;
54  if (interval != 0)
55  offset = (random_uint32() % interval) / 2;
56 
57  ESP_LOGVV(TAG, "set_interval(name='%s', interval=%u, offset=%u)", name.c_str(), interval, offset);
58 
59  auto item = make_unique<SchedulerItem>();
60  item->component = component;
61  item->name = name;
62  item->type = SchedulerItem::INTERVAL;
63  item->interval = interval;
64  item->last_execution = now - offset - interval;
65  item->last_execution_major = this->millis_major_;
66  if (item->last_execution > now)
67  item->last_execution_major--;
68  item->callback = std::move(func);
69  item->remove = false;
70  this->push_(std::move(item));
71 }
72 bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
73  return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
74 }
75 
76 struct RetryArgs {
77  std::function<RetryResult()> func;
78  uint8_t retry_countdown;
79  uint32_t current_interval;
80  Component *component;
81  std::string name;
82  float backoff_increase_factor;
83  Scheduler *scheduler;
84 };
85 
86 static void retry_handler(const std::shared_ptr<RetryArgs> &args) {
87  RetryResult retry_result = args->func();
88  if (retry_result == RetryResult::DONE || --args->retry_countdown <= 0)
89  return;
90  args->current_interval *= args->backoff_increase_factor;
91  args->scheduler->set_timeout(args->component, args->name, args->current_interval, [args]() { retry_handler(args); });
92 }
93 
94 void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
95  uint8_t max_attempts, std::function<RetryResult()> func, float backoff_increase_factor) {
96  if (!name.empty())
97  this->cancel_retry(component, name);
98 
99  if (initial_wait_time == SCHEDULER_DONT_RUN)
100  return;
101 
102  ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
103  initial_wait_time, max_attempts, backoff_increase_factor);
104 
105  auto args = std::make_shared<RetryArgs>();
106  args->func = std::move(func);
107  args->retry_countdown = max_attempts;
108  args->current_interval = initial_wait_time;
109  args->component = component;
110  args->name = "retry$" + name;
111  args->backoff_increase_factor = backoff_increase_factor;
112  args->scheduler = this;
113 
114  this->set_timeout(component, args->name, initial_wait_time, [args]() { retry_handler(args); });
115 }
116 bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
117  return this->cancel_timeout(component, "retry$" + name);
118 }
119 
121  if (this->empty_())
122  return {};
123  auto &item = this->items_[0];
124  const uint32_t now = this->millis_();
125  uint32_t next_time = item->last_execution + item->interval;
126  if (next_time < now)
127  return 0;
128  return next_time - now;
129 }
130 void HOT Scheduler::call() {
131  const uint32_t now = this->millis_();
132  this->process_to_add();
133 
134 #ifdef ESPHOME_DEBUG_SCHEDULER
135  static uint32_t last_print = 0;
136 
137  if (now - last_print > 2000) {
138  last_print = now;
139  std::vector<std::unique_ptr<SchedulerItem>> old_items;
140  ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
141  while (!this->empty_()) {
142  auto item = std::move(this->items_[0]);
143  ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
144  item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
145  item->next_execution(), item->next_execution_major());
146 
147  this->pop_raw_();
148  old_items.push_back(std::move(item));
149  }
150  ESP_LOGVV(TAG, "\n");
151  this->items_ = std::move(old_items);
152  }
153 #endif // ESPHOME_DEBUG_SCHEDULER
154 
155  auto to_remove_was = to_remove_;
156  auto items_was = items_.size();
157  // If we have too many items to remove
158  if (to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
159  std::vector<std::unique_ptr<SchedulerItem>> valid_items;
160  while (!this->empty_()) {
161  auto item = std::move(this->items_[0]);
162  this->pop_raw_();
163  valid_items.push_back(std::move(item));
164  }
165  this->items_ = std::move(valid_items);
166 
167  // The following should not happen unless I'm missing something
168  if (to_remove_ != 0) {
169  ESP_LOGW(TAG, "to_remove_ was %u now: %u items where %zu now %zu. Please report this", to_remove_was, to_remove_,
170  items_was, items_.size());
171  to_remove_ = 0;
172  }
173  }
174 
175  while (!this->empty_()) {
176  // use scoping to indicate visibility of `item` variable
177  {
178  // Don't copy-by value yet
179  auto &item = this->items_[0];
180  if ((now - item->last_execution) < item->interval) {
181  // Not reached timeout yet, done for this call
182  break;
183  }
184  uint8_t major = item->next_execution_major();
185  if (this->millis_major_ - major > 1)
186  break;
187 
188  // Don't run on failed components
189  if (item->component != nullptr && item->component->is_failed()) {
190  this->pop_raw_();
191  continue;
192  }
193 
194 #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
195  ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
196  item->name.c_str(), item->interval, item->last_execution, now);
197 #endif
198 
199  // Warning: During callback(), a lot of stuff can happen, including:
200  // - timeouts/intervals get added, potentially invalidating vector pointers
201  // - timeouts/intervals get cancelled
202  {
203  WarnIfComponentBlockingGuard guard{item->component};
204  item->callback();
205  }
206  }
207 
208  {
209  // new scope, item from before might have been moved in the vector
210  auto item = std::move(this->items_[0]);
211 
212  // Only pop after function call, this ensures we were reachable
213  // during the function call and know if we were cancelled.
214  this->pop_raw_();
215 
216  if (item->remove) {
217  // We were removed/cancelled in the function call, stop
218  to_remove_--;
219  continue;
220  }
221 
222  if (item->type == SchedulerItem::INTERVAL) {
223  if (item->interval != 0) {
224  const uint32_t before = item->last_execution;
225  const uint32_t amount = (now - item->last_execution) / item->interval;
226  item->last_execution += amount * item->interval;
227  if (item->last_execution < before)
228  item->last_execution_major++;
229  }
230  this->push_(std::move(item));
231  }
232  }
233  }
234 
235  this->process_to_add();
236 }
238  for (auto &it : this->to_add_) {
239  if (it->remove) {
240  continue;
241  }
242 
243  this->items_.push_back(std::move(it));
244  std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
245  }
246  this->to_add_.clear();
247 }
249  while (!this->items_.empty()) {
250  auto &item = this->items_[0];
251  if (!item->remove)
252  return;
253 
254  to_remove_--;
255  this->pop_raw_();
256  }
257 }
259  std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
260  this->items_.pop_back();
261 }
262 void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) { this->to_add_.push_back(std::move(item)); }
263 bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) {
264  bool ret = false;
265  for (auto &it : this->items_) {
266  if (it->component == component && it->name == name && it->type == type && !it->remove) {
267  to_remove_++;
268  it->remove = true;
269  ret = true;
270  }
271  }
272  for (auto &it : this->to_add_) {
273  if (it->component == component && it->name == name && it->type == type) {
274  it->remove = true;
275  ret = true;
276  }
277  }
278 
279  return ret;
280 }
281 uint32_t Scheduler::millis_() {
282  const uint32_t now = millis();
283  if (now < this->last_millis_) {
284  ESP_LOGD(TAG, "Incrementing scheduler major");
285  this->millis_major_++;
286  }
287  this->last_millis_ = now;
288  return now;
289 }
290 
291 bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
292  const std::unique_ptr<SchedulerItem> &b) {
293  // min-heap
294  // return true if *a* will happen after *b*
295  uint32_t a_next_exec = a->next_execution();
296  uint8_t a_next_exec_major = a->next_execution_major();
297  uint32_t b_next_exec = b->next_execution();
298  uint8_t b_next_exec_major = b->next_execution_major();
299 
300  if (a_next_exec_major != b_next_exec_major) {
301  // The "major" calculation is quite complicated.
302  // Basically, we need to check if the major value lies in the future or
303  //
304 
305  // Here are some cases to think about:
306  // Format: a_major,b_major -> expected result (a-b, b-a)
307  // a=255,b=0 -> false (255, 1)
308  // a=0,b=1 -> false (255, 1)
309  // a=1,b=0 -> true (1, 255)
310  // a=0,b=255 -> true (1, 255)
311 
312  uint8_t diff1 = a_next_exec_major - b_next_exec_major;
313  uint8_t diff2 = b_next_exec_major - a_next_exec_major;
314  return diff1 < diff2;
315  }
316 
317  return a_next_exec > b_next_exec;
318 }
319 
320 } // namespace esphome
uint32_t to_remove_
Definition: scheduler.h:78
const char * name
Definition: stm32flash.h:78
RetryResult
Definition: component.h:64
void push_(std::unique_ptr< SchedulerItem > item)
Definition: scheduler.cpp:262
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition: helpers.cpp:74
bool cancel_timeout(Component *component, const std::string &name)
Definition: scheduler.cpp:39
optional< uint32_t > next_schedule_in()
Definition: scheduler.cpp:120
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
uint8_t millis_major_
Definition: scheduler.h:77
uint32_t millis_()
Definition: scheduler.cpp:281
std::vector< std::unique_ptr< SchedulerItem > > to_add_
Definition: scheduler.h:75
uint8_t type
bool cancel_retry(Component *component, const std::string &name)
Definition: scheduler.cpp:116
uint32_t last_millis_
Definition: scheduler.h:76
bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type)
Definition: scheduler.cpp:263
static bool cmp(const std::unique_ptr< SchedulerItem > &a, const std::unique_ptr< SchedulerItem > &b)
Definition: scheduler.cpp:291
bool cancel_interval(Component *component, const std::string &name)
Definition: scheduler.cpp:72
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function< void()> func)
Definition: scheduler.cpp:16
Definition: a4988.cpp:4
std::vector< std::unique_ptr< SchedulerItem > > items_
Definition: scheduler.h:74
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)
Definition: scheduler.cpp:94
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function< void()> func)
Definition: scheduler.cpp:42