1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-25 14:42:21 +01:00

Lock scheduler items while modifying them (#4410)

* Cosmetic fixes to scheduler code

* Add generic Mutex API

* Lock scheduler items while modifying them

* Always defer MQTT callbacks on Arduino
This commit is contained in:
Oxan van Leeuwen
2023-02-26 19:43:08 +01:00
committed by GitHub
parent 1a9141877d
commit 86c0e6114f
5 changed files with 100 additions and 12 deletions

View File

@@ -13,6 +13,11 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
// Uncomment to debug scheduler
// #define ESPHOME_DEBUG_SCHEDULER
// A note on locking: the `lock_` lock protects the `items_` and `to_add_` containers. It must be taken when writing to
// them (i.e. when adding/removing items, but not when changing items). As items are only deleted from the loop task,
// iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to
// avoid the main thread modifying the list while it is being accessed.
void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> func) {
const uint32_t now = this->millis_();
@@ -121,7 +126,7 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
args->backoff_increase_factor = backoff_increase_factor;
args->scheduler = this;
// First exectuion of `func` immediately
// First execution of `func` immediately
this->set_timeout(component, args->name, 0, [args]() { retry_handler(args); });
}
bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
@@ -150,30 +155,42 @@ void HOT Scheduler::call() {
std::vector<std::unique_ptr<SchedulerItem>> old_items;
ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
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=%u last_execution=%u (%u) next=%u (%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());
this->pop_raw_();
old_items.push_back(std::move(item));
}
ESP_LOGVV(TAG, "\n");
this->items_ = std::move(old_items);
{
LockGuard guard{this->lock_};
this->items_ = std::move(old_items);
}
}
#endif // ESPHOME_DEBUG_SCHEDULER
auto to_remove_was = to_remove_;
auto items_was = items_.size();
auto items_was = this->items_.size();
// If we have too many items to remove
if (to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
while (!this->empty_()) {
LockGuard guard{this->lock_};
auto item = std::move(this->items_[0]);
this->pop_raw_();
valid_items.push_back(std::move(item));
}
this->items_ = std::move(valid_items);
{
LockGuard guard{this->lock_};
this->items_ = std::move(valid_items);
}
// The following should not happen unless I'm missing something
if (to_remove_ != 0) {
@@ -198,6 +215,7 @@ void HOT Scheduler::call() {
// Don't run on failed components
if (item->component != nullptr && item->component->is_failed()) {
LockGuard guard{this->lock_};
this->pop_raw_();
continue;
}
@@ -217,6 +235,8 @@ void HOT Scheduler::call() {
}
{
this->lock_.lock();
// new scope, item from before might have been moved in the vector
auto item = std::move(this->items_[0]);
@@ -224,6 +244,8 @@ void HOT Scheduler::call() {
// during the function call and know if we were cancelled.
this->pop_raw_();
this->lock_.unlock();
if (item->remove) {
// We were removed/cancelled in the function call, stop
to_remove_--;
@@ -246,6 +268,7 @@ void HOT Scheduler::call() {
this->process_to_add();
}
void HOT Scheduler::process_to_add() {
LockGuard guard{this->lock_};
for (auto &it : this->to_add_) {
if (it->remove) {
continue;
@@ -263,15 +286,24 @@ void HOT Scheduler::cleanup_() {
return;
to_remove_--;
this->pop_raw_();
{
LockGuard guard{this->lock_};
this->pop_raw_();
}
}
}
void HOT Scheduler::pop_raw_() {
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
this->items_.pop_back();
}
void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) { this->to_add_.push_back(std::move(item)); }
void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) {
LockGuard guard{this->lock_};
this->to_add_.push_back(std::move(item));
}
bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) {
// obtain lock because this function iterates and can be called from non-loop task context
LockGuard guard{this->lock_};
bool ret = false;
for (auto &it : this->items_) {
if (it->component == component && it->name == name && it->type == type && !it->remove) {