mirror of
https://github.com/esphome/esphome.git
synced 2026-02-10 01:32:06 +00:00
Compare commits
1 Commits
dev
...
scheduler-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1328f1b3a |
@@ -107,6 +107,24 @@ static void validate_static_string(const char *name) {
|
||||
// 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.
|
||||
|
||||
// Calculate random offset for interval timers
|
||||
// Extracted from set_timer_common_ to reduce code size - float math + random_float()
|
||||
// only needed for intervals, not timeouts
|
||||
uint32_t Scheduler::calculate_interval_offset_(uint32_t delay) {
|
||||
return static_cast<uint32_t>(std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float());
|
||||
}
|
||||
|
||||
// Check if a retry was already cancelled in items_ or to_add_
|
||||
// Extracted from set_timer_common_ to reduce code size - retry path is cold and deprecated
|
||||
// Remove before 2026.8.0 along with all retry code
|
||||
bool Scheduler::is_retry_cancelled_locked_(Component *component, NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id) {
|
||||
return has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true) ||
|
||||
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true);
|
||||
}
|
||||
|
||||
// Common implementation for both timeout and interval
|
||||
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
|
||||
void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type,
|
||||
@@ -130,84 +148,66 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
// Create and populate the scheduler item
|
||||
auto item = this->get_item_from_pool_locked_();
|
||||
item->component = component;
|
||||
switch (name_type) {
|
||||
case NameType::STATIC_STRING:
|
||||
item->set_static_name(static_name);
|
||||
break;
|
||||
case NameType::HASHED_STRING:
|
||||
item->set_hashed_name(hash_or_id);
|
||||
break;
|
||||
case NameType::NUMERIC_ID:
|
||||
item->set_numeric_id(hash_or_id);
|
||||
break;
|
||||
case NameType::NUMERIC_ID_INTERNAL:
|
||||
item->set_internal_id(hash_or_id);
|
||||
break;
|
||||
}
|
||||
item->set_name(name_type, static_name, hash_or_id);
|
||||
item->type = type;
|
||||
item->callback = std::move(func);
|
||||
// Reset remove flag - recycled items may have been cancelled (remove=true) in previous use
|
||||
this->set_item_removed_(item.get(), false);
|
||||
item->is_retry = is_retry;
|
||||
|
||||
// Determine target container: defer_queue_ for deferred items, to_add_ for everything else.
|
||||
// Using a pointer lets both paths share the cancel + push_back epilogue.
|
||||
auto *target = &this->to_add_;
|
||||
|
||||
#ifndef ESPHOME_THREAD_SINGLE
|
||||
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
||||
// Single-core platforms don't need thread-safe defer handling
|
||||
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
||||
// Put in defer queue for guaranteed FIFO execution
|
||||
if (!skip_cancel) {
|
||||
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type);
|
||||
}
|
||||
this->defer_queue_.push_back(std::move(item));
|
||||
return;
|
||||
}
|
||||
target = &this->defer_queue_;
|
||||
} else
|
||||
#endif /* not ESPHOME_THREAD_SINGLE */
|
||||
|
||||
// Type-specific setup
|
||||
if (type == SchedulerItem::INTERVAL) {
|
||||
item->interval = delay;
|
||||
// first execution happens immediately after a random smallish offset
|
||||
// Calculate random offset (0 to min(interval/2, 5s))
|
||||
uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float());
|
||||
item->set_next_execution(now + offset);
|
||||
{
|
||||
// Type-specific setup
|
||||
if (type == SchedulerItem::INTERVAL) {
|
||||
item->interval = delay;
|
||||
// first execution happens immediately after a random smallish offset
|
||||
uint32_t offset = this->calculate_interval_offset_(delay);
|
||||
item->set_next_execution(now + offset);
|
||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||
SchedulerNameLog name_log;
|
||||
ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms",
|
||||
name_log.format(name_type, static_name, hash_or_id), delay, offset);
|
||||
SchedulerNameLog name_log;
|
||||
ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms",
|
||||
name_log.format(name_type, static_name, hash_or_id), delay, offset);
|
||||
#endif
|
||||
} else {
|
||||
item->interval = 0;
|
||||
item->set_next_execution(now + delay);
|
||||
}
|
||||
} else {
|
||||
item->interval = 0;
|
||||
item->set_next_execution(now + delay);
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
this->debug_log_timer_(item.get(), name_type, static_name, hash_or_id, type, delay, now);
|
||||
this->debug_log_timer_(item.get(), name_type, static_name, hash_or_id, type, delay, now);
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
// For retries, check if there's a cancelled timeout first
|
||||
// Skip check for anonymous retries (STATIC_STRING with nullptr) - they can't be cancelled by name
|
||||
if (is_retry && (name_type != NameType::STATIC_STRING || static_name != nullptr) && type == SchedulerItem::TIMEOUT &&
|
||||
(has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true) ||
|
||||
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id,
|
||||
/* match_retry= */ true))) {
|
||||
// Skip scheduling - the retry was cancelled
|
||||
// For retries, check if there's a cancelled timeout first
|
||||
// Skip check for anonymous retries (STATIC_STRING with nullptr) - they can't be cancelled by name
|
||||
if (is_retry && (name_type != NameType::STATIC_STRING || static_name != nullptr) &&
|
||||
type == SchedulerItem::TIMEOUT &&
|
||||
this->is_retry_cancelled_locked_(component, name_type, static_name, hash_or_id)) {
|
||||
// Skip scheduling - the retry was cancelled
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
SchedulerNameLog skip_name_log;
|
||||
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item",
|
||||
skip_name_log.format(name_type, static_name, hash_or_id));
|
||||
SchedulerNameLog skip_name_log;
|
||||
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item",
|
||||
skip_name_log.format(name_type, static_name, hash_or_id));
|
||||
#endif
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If name is provided, do atomic cancel-and-add (unless skip_cancel is true)
|
||||
// Cancel existing items
|
||||
// Common epilogue: atomic cancel-and-add (unless skip_cancel is true)
|
||||
if (!skip_cancel) {
|
||||
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type);
|
||||
}
|
||||
// Add new item directly to to_add_
|
||||
// since we have the lock held
|
||||
this->to_add_.push_back(std::move(item));
|
||||
target->push_back(std::move(item));
|
||||
}
|
||||
|
||||
void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func) {
|
||||
|
||||
@@ -219,28 +219,15 @@ class Scheduler {
|
||||
// Helper to get the name type
|
||||
NameType get_name_type() const { return name_type_; }
|
||||
|
||||
// Helper to set a static string name (no allocation)
|
||||
void set_static_name(const char *name) {
|
||||
name_.static_name = name;
|
||||
name_type_ = NameType::STATIC_STRING;
|
||||
}
|
||||
|
||||
// Helper to set a hashed string name (hash computed from std::string)
|
||||
void set_hashed_name(uint32_t hash) {
|
||||
name_.hash_or_id = hash;
|
||||
name_type_ = NameType::HASHED_STRING;
|
||||
}
|
||||
|
||||
// Helper to set a numeric ID name
|
||||
void set_numeric_id(uint32_t id) {
|
||||
name_.hash_or_id = id;
|
||||
name_type_ = NameType::NUMERIC_ID;
|
||||
}
|
||||
|
||||
// Helper to set an internal numeric ID (separate namespace from NUMERIC_ID)
|
||||
void set_internal_id(uint32_t id) {
|
||||
name_.hash_or_id = id;
|
||||
name_type_ = NameType::NUMERIC_ID_INTERNAL;
|
||||
// Set name storage: for STATIC_STRING stores the pointer, for all other types stores hash_or_id.
|
||||
// Both union members occupy the same offset, so only one store is needed.
|
||||
void set_name(NameType type, const char *static_name, uint32_t hash_or_id) {
|
||||
if (type == NameType::STATIC_STRING) {
|
||||
name_.static_name = static_name;
|
||||
} else {
|
||||
name_.hash_or_id = hash_or_id;
|
||||
}
|
||||
name_type_ = type;
|
||||
}
|
||||
|
||||
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
||||
@@ -355,6 +342,17 @@ class Scheduler {
|
||||
// Helper to perform full cleanup when too many items are cancelled
|
||||
void full_cleanup_removed_items_();
|
||||
|
||||
// Helper to calculate random offset for interval timers - extracted to reduce code size of set_timer_common_
|
||||
// IMPORTANT: Must not be inlined - called only for intervals, keeping it out of the hot path saves flash.
|
||||
uint32_t __attribute__((noinline)) calculate_interval_offset_(uint32_t delay);
|
||||
|
||||
// Helper to check if a retry was already cancelled - extracted to reduce code size of set_timer_common_
|
||||
// Remove before 2026.8.0 along with all retry code.
|
||||
// IMPORTANT: Must not be inlined - retry path is cold and deprecated.
|
||||
// IMPORTANT: Caller must hold the scheduler lock before calling this function.
|
||||
bool __attribute__((noinline))
|
||||
is_retry_cancelled_locked_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
|
||||
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
// Helper for debug logging in set_timer_common_ - extracted to reduce code size
|
||||
void debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, uint32_t hash_or_id,
|
||||
|
||||
Reference in New Issue
Block a user