mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 14:43:51 +00:00
fix defer churn
This commit is contained in:
@@ -101,6 +101,22 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
// Take lock early to protect scheduler_item_pool_ access
|
// Take lock early to protect scheduler_item_pool_ access
|
||||||
LockGuard guard{this->lock_};
|
LockGuard guard{this->lock_};
|
||||||
|
|
||||||
|
// Optimization: if we're updating a defer that hasn't executed yet, just update its callback
|
||||||
|
// This avoids allocating a new item and cancelling/re-adding
|
||||||
|
if (delay == 0 && type == SchedulerItem::TIMEOUT && !skip_cancel && name_cstr != nullptr) {
|
||||||
|
#ifdef ESPHOME_THREAD_SINGLE
|
||||||
|
// Single-threaded: check to_add_ for defers that haven't been moved to heap yet
|
||||||
|
if (this->try_update_defer_in_container_(this->to_add_, component, name_cstr, std::move(func))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Multi-threaded: check defer_queue_
|
||||||
|
if (this->try_update_defer_in_container_(this->defer_queue_, component, name_cstr, std::move(func))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Create and populate the scheduler item
|
// Create and populate the scheduler item
|
||||||
std::unique_ptr<SchedulerItem> item;
|
std::unique_ptr<SchedulerItem> item;
|
||||||
if (!this->scheduler_item_pool_.empty()) {
|
if (!this->scheduler_item_pool_.empty()) {
|
||||||
|
|||||||
@@ -217,6 +217,15 @@ class Scheduler {
|
|||||||
// Common implementation for cancel operations
|
// Common implementation for cancel operations
|
||||||
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
|
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
|
||||||
|
|
||||||
|
// Helper to check if two scheduler item names match
|
||||||
|
inline bool HOT names_match_(const char *name1, const char *name2) const {
|
||||||
|
// Check pointer equality first (common for static strings), then string contents
|
||||||
|
// The core ESPHome codebase uses static strings (const char*) for component names,
|
||||||
|
// making pointer comparison effective. The std::string overloads exist only for
|
||||||
|
// compatibility with external components but are rarely used in practice.
|
||||||
|
return (name1 != nullptr && name2 != nullptr) && ((name1 == name2) || (strcmp(name1, name2) == 0));
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to check if item matches criteria for cancellation
|
// 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,
|
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
|
||||||
SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
|
SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
|
||||||
@@ -224,19 +233,7 @@ class Scheduler {
|
|||||||
(match_retry && !item->is_retry)) {
|
(match_retry && !item->is_retry)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const char *item_name = item->get_name();
|
return this->names_match_(item->get_name(), name_cstr);
|
||||||
if (item_name == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Fast path: if pointers are equal
|
|
||||||
// This is effective because the core ESPHome codebase uses static strings (const char*)
|
|
||||||
// for component names. The std::string overloads exist only for compatibility with
|
|
||||||
// external components, but are rarely used in practice.
|
|
||||||
if (item_name == name_cstr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Slow path: compare string contents
|
|
||||||
return strcmp(name_cstr, item_name) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to execute a scheduler item
|
// Helper to execute a scheduler item
|
||||||
@@ -313,6 +310,28 @@ class Scheduler {
|
|||||||
return cancelled;
|
return cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Template helper to try updating a defer in a container instead of allocating a new one
|
||||||
|
// Returns true if the defer was updated, false if not found
|
||||||
|
template<typename Container>
|
||||||
|
bool try_update_defer_in_container_(Container &container, Component *component, const char *name_cstr,
|
||||||
|
std::function<void()> &&func) {
|
||||||
|
if (container.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &last_item = container.back();
|
||||||
|
|
||||||
|
// Check if last item is a matching defer (timeout with 0 delay) and names match
|
||||||
|
if (last_item->component != component || last_item->type != SchedulerItem::TIMEOUT || last_item->interval != 0 ||
|
||||||
|
is_item_removed_(last_item.get()) || !this->names_match_(last_item->get_name(), name_cstr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same defer at the end - just update the callback, no allocation needed
|
||||||
|
last_item->callback = std::move(func);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Mutex lock_;
|
Mutex lock_;
|
||||||
std::vector<std::unique_ptr<SchedulerItem>> items_;
|
std::vector<std::unique_ptr<SchedulerItem>> items_;
|
||||||
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
|
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
|
||||||
|
|||||||
Reference in New Issue
Block a user