1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-25 22:52:20 +01:00

[scheduler] Properly handle millis() overflow (#8197)

This commit is contained in:
Clyde Stubbs
2025-03-27 18:09:22 +11:00
committed by GitHub
parent ccd55a8e84
commit 58fe8b39b2
14 changed files with 85 additions and 86 deletions

View File

@@ -1,4 +1,5 @@
#include "scheduler.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/hal.h"
@@ -21,7 +22,7 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> func) {
const uint32_t now = this->millis_();
const auto now = this->millis_();
if (!name.empty())
this->cancel_timeout(component, name);
@@ -29,17 +30,16 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
if (timeout == SCHEDULER_DONT_RUN)
return;
ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%" PRIu32 ")", name.c_str(), timeout);
auto item = make_unique<SchedulerItem>();
item->component = component;
item->name = name;
item->type = SchedulerItem::TIMEOUT;
item->timeout = timeout;
item->last_execution = now;
item->last_execution_major = this->millis_major_;
item->next_execution_ = now + timeout;
item->callback = std::move(func);
item->remove = false;
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "set_timeout(name='%s/%s', timeout=%" PRIu32 ")", item->get_source(), name.c_str(), timeout);
#endif
this->push_(std::move(item));
}
bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) {
@@ -47,7 +47,7 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name
}
void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
std::function<void()> func) {
const uint32_t now = this->millis_();
const auto now = this->millis_();
if (!name.empty())
this->cancel_interval(component, name);
@@ -60,19 +60,18 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
if (interval != 0)
offset = (random_uint32() % interval) / 2;
ESP_LOGVV(TAG, "set_interval(name='%s', interval=%" PRIu32 ", offset=%" PRIu32 ")", name.c_str(), interval, offset);
auto item = make_unique<SchedulerItem>();
item->component = component;
item->name = name;
item->type = SchedulerItem::INTERVAL;
item->interval = interval;
item->last_execution = now - offset - interval;
item->last_execution_major = this->millis_major_;
if (item->last_execution > now)
item->last_execution_major--;
item->next_execution_ = now + offset;
item->callback = std::move(func);
item->remove = false;
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "set_interval(name='%s/%s', interval=%" PRIu32 ", offset=%" PRIu32 ")", item->get_source(),
name.c_str(), interval, offset);
#endif
this->push_(std::move(item));
}
bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
@@ -138,36 +137,36 @@ optional<uint32_t> HOT Scheduler::next_schedule_in() {
if (this->empty_())
return {};
auto &item = this->items_[0];
const uint32_t now = this->millis_();
uint32_t next_time = item->last_execution + item->interval;
if (next_time < now)
const auto now = this->millis_();
if (item->next_execution_ < now)
return 0;
return next_time - now;
return item->next_execution_ - now;
}
void HOT Scheduler::call() {
const uint32_t now = this->millis_();
const auto now = this->millis_();
this->process_to_add();
#ifdef ESPHOME_DEBUG_SCHEDULER
static uint32_t last_print = 0;
static uint64_t last_print = 0;
if (now - last_print > 2000) {
last_print = now;
std::vector<std::unique_ptr<SchedulerItem>> old_items;
ESP_LOGVV(TAG, "Items: count=%u, now=%" PRIu32, this->items_.size(), now);
ESP_LOGD(TAG, "Items: count=%u, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now, this->millis_major_,
this->last_millis_);
while (!this->empty_()) {
this->lock_.lock();
auto item = std::move(this->items_[0]);
this->pop_raw_();
this->lock_.unlock();
ESP_LOGVV(TAG, " %s '%s' interval=%" PRIu32 " last_execution=%" PRIu32 " (%u) next=%" PRIu32 " (%u)",
item->get_type_str(), item->name.c_str(), item->interval, item->last_execution,
item->last_execution_major, item->next_execution(), item->next_execution_major());
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
item->get_type_str(), item->get_source(), item->name.c_str(), item->interval,
item->next_execution_ - now, item->next_execution_);
old_items.push_back(std::move(item));
}
ESP_LOGVV(TAG, "\n");
ESP_LOGD(TAG, "\n");
{
LockGuard guard{this->lock_};
@@ -206,14 +205,10 @@ void HOT Scheduler::call() {
{
// Don't copy-by value yet
auto &item = this->items_[0];
if ((now - item->last_execution) < item->interval) {
if (item->next_execution_ > now) {
// Not reached timeout yet, done for this call
break;
}
uint8_t major = item->next_execution_major();
if (this->millis_major_ - major > 1)
break;
// Don't run on failed components
if (item->component != nullptr && item->component->is_failed()) {
LockGuard guard{this->lock_};
@@ -221,9 +216,10 @@ void HOT Scheduler::call() {
continue;
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "Running %s '%s' with interval=%" PRIu32 " last_execution=%" PRIu32 " (now=%" PRIu32 ")",
item->get_type_str(), item->name.c_str(), item->interval, item->last_execution, now);
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
item->get_type_str(), item->get_source(), item->name.c_str(), item->interval, item->next_execution_,
now);
#endif
// Warning: During callback(), a lot of stuff can happen, including:
@@ -254,13 +250,7 @@ void HOT Scheduler::call() {
}
if (item->type == SchedulerItem::INTERVAL) {
if (item->interval != 0) {
const uint32_t before = item->last_execution;
const uint32_t amount = (now - item->last_execution) / item->interval;
item->last_execution += amount * item->interval;
if (item->last_execution < before)
item->last_execution_major++;
}
item->next_execution_ = now + item->interval;
this->push_(std::move(item));
}
}
@@ -322,43 +312,20 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name,
return ret;
}
uint32_t Scheduler::millis_() {
uint64_t Scheduler::millis_() {
const uint32_t now = millis();
if (now < this->last_millis_) {
ESP_LOGD(TAG, "Incrementing scheduler major");
this->millis_major_++;
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
now + (static_cast<uint64_t>(this->millis_major_) << 32));
}
this->last_millis_ = now;
return now;
return now + (static_cast<uint64_t>(this->millis_major_) << 32);
}
bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
const std::unique_ptr<SchedulerItem> &b) {
// min-heap
// return true if *a* will happen after *b*
uint32_t a_next_exec = a->next_execution();
uint8_t a_next_exec_major = a->next_execution_major();
uint32_t b_next_exec = b->next_execution();
uint8_t b_next_exec_major = b->next_execution_major();
if (a_next_exec_major != b_next_exec_major) {
// The "major" calculation is quite complicated.
// Basically, we need to check if the major value lies in the future or
//
// Here are some cases to think about:
// Format: a_major,b_major -> expected result (a-b, b-a)
// a=255,b=0 -> false (255, 1)
// a=0,b=1 -> false (255, 1)
// a=1,b=0 -> true (1, 255)
// a=0,b=255 -> true (1, 255)
uint8_t diff1 = a_next_exec_major - b_next_exec_major;
uint8_t diff2 = b_next_exec_major - a_next_exec_major;
return diff1 < diff2;
}
return a_next_exec > b_next_exec;
return a->next_execution_ > b->next_execution_;
}
} // namespace esphome