mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 11:22:24 +01:00
atomic remove
This commit is contained in:
@@ -82,7 +82,13 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
item->set_name(name_cstr, !is_static_string);
|
item->set_name(name_cstr, !is_static_string);
|
||||||
item->type = type;
|
item->type = type;
|
||||||
item->callback = std::move(func);
|
item->callback = std::move(func);
|
||||||
|
// Initialize remove to false (though it should already be from constructor)
|
||||||
|
// Not using mark_item_removed_ helper since we're setting to false, not true
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
item->remove.store(false, std::memory_order_relaxed);
|
||||||
|
#else
|
||||||
item->remove = false;
|
item->remove = false;
|
||||||
|
#endif
|
||||||
item->is_retry = is_retry;
|
item->is_retry = is_retry;
|
||||||
|
|
||||||
#ifndef ESPHOME_THREAD_SINGLE
|
#ifndef ESPHOME_THREAD_SINGLE
|
||||||
@@ -398,6 +404,31 @@ void HOT Scheduler::call(uint32_t now) {
|
|||||||
this->pop_raw_();
|
this->pop_raw_();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if item is marked for removal
|
||||||
|
// This handles two cases:
|
||||||
|
// 1. Item was marked for removal after cleanup_() but before we got here
|
||||||
|
// 2. Item is marked for removal but wasn't at the front of the heap during cleanup_()
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
|
||||||
|
// Multi-threaded platforms without atomics: must take lock to safely read remove flag
|
||||||
|
{
|
||||||
|
LockGuard guard{this->lock_};
|
||||||
|
if (is_item_removed_(item.get())) {
|
||||||
|
this->pop_raw_();
|
||||||
|
this->to_remove_--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Single-threaded or multi-threaded with atomics: can check without lock
|
||||||
|
if (is_item_removed_(item.get())) {
|
||||||
|
LockGuard guard{this->lock_};
|
||||||
|
this->pop_raw_();
|
||||||
|
this->to_remove_--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
const char *item_name = item->get_name();
|
const char *item_name = item->get_name();
|
||||||
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
||||||
@@ -518,7 +549,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
if (type == SchedulerItem::TIMEOUT) {
|
if (type == SchedulerItem::TIMEOUT) {
|
||||||
for (auto &item : this->defer_queue_) {
|
for (auto &item : this->defer_queue_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
this->mark_item_removed_(item.get());
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,7 +559,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
// Cancel items in the main heap
|
// Cancel items in the main heap
|
||||||
for (auto &item : this->items_) {
|
for (auto &item : this->items_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
this->mark_item_removed_(item.get());
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
this->to_remove_++; // Track removals for heap items
|
this->to_remove_++; // Track removals for heap items
|
||||||
}
|
}
|
||||||
@@ -537,7 +568,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
// Cancel items in to_add_
|
// Cancel items in to_add_
|
||||||
for (auto &item : this->to_add_) {
|
for (auto &item : this->to_add_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
this->mark_item_removed_(item.get());
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
// Don't track removals for to_add_ items
|
// Don't track removals for to_add_ items
|
||||||
}
|
}
|
||||||
|
@@ -97,22 +97,42 @@ class Scheduler {
|
|||||||
|
|
||||||
std::function<void()> callback;
|
std::function<void()> callback;
|
||||||
|
|
||||||
// Bit-packed fields to minimize padding
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
// Multi-threaded with atomics: use atomic for lock-free access
|
||||||
|
// Place atomic<bool> separately since it can't be packed with bit fields
|
||||||
|
std::atomic<bool> remove{false};
|
||||||
|
|
||||||
|
// Bit-packed fields (3 bits used, 5 bits padding in 1 byte)
|
||||||
|
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||||
|
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
|
||||||
|
bool is_retry : 1; // True if this is a retry timeout
|
||||||
|
// 5 bits padding
|
||||||
|
#else
|
||||||
|
// Single-threaded or multi-threaded without atomics: can pack all fields together
|
||||||
|
// Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
|
||||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||||
bool remove : 1;
|
bool remove : 1;
|
||||||
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
|
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
|
||||||
bool is_retry : 1; // True if this is a retry timeout
|
bool is_retry : 1; // True if this is a retry timeout
|
||||||
// 4 bits padding
|
// 4 bits padding
|
||||||
|
#endif
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
SchedulerItem()
|
SchedulerItem()
|
||||||
: component(nullptr),
|
: component(nullptr),
|
||||||
interval(0),
|
interval(0),
|
||||||
next_execution_(0),
|
next_execution_(0),
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
// remove is initialized in the member declaration as std::atomic<bool>{false}
|
||||||
|
type(TIMEOUT),
|
||||||
|
name_is_dynamic(false),
|
||||||
|
is_retry(false) {
|
||||||
|
#else
|
||||||
type(TIMEOUT),
|
type(TIMEOUT),
|
||||||
remove(false),
|
remove(false),
|
||||||
name_is_dynamic(false),
|
name_is_dynamic(false),
|
||||||
is_retry(false) {
|
is_retry(false) {
|
||||||
|
#endif
|
||||||
name_.static_name = nullptr;
|
name_.static_name = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +239,35 @@ class Scheduler {
|
|||||||
return item->remove || (item->component != nullptr && item->component->is_failed());
|
return item->remove || (item->component != nullptr && item->component->is_failed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to check if item is marked for removal (platform-specific)
|
||||||
|
// Returns true if item should be skipped, handles platform-specific synchronization
|
||||||
|
// NOTE: For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller must hold lock!
|
||||||
|
bool is_item_removed_(SchedulerItem *item) const {
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
// Multi-threaded with atomics: use atomic load for lock-free access
|
||||||
|
return item->remove.load(std::memory_order_acquire);
|
||||||
|
#else
|
||||||
|
// Single-threaded (ESPHOME_THREAD_SINGLE) or
|
||||||
|
// multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct read
|
||||||
|
// For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock!
|
||||||
|
return item->remove;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to mark item for removal (platform-specific)
|
||||||
|
// NOTE: For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller must hold lock!
|
||||||
|
void mark_item_removed_(SchedulerItem *item) {
|
||||||
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
|
// Multi-threaded with atomics: use atomic store
|
||||||
|
item->remove.store(true, std::memory_order_release);
|
||||||
|
#else
|
||||||
|
// Single-threaded (ESPHOME_THREAD_SINGLE) or
|
||||||
|
// multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct write
|
||||||
|
// For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock!
|
||||||
|
item->remove = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Template helper to check if any item in a container matches our criteria
|
// Template helper to check if any item in a container matches our criteria
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
|
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
|
||||||
|
Reference in New Issue
Block a user