15static const char *
const TAG =
"scheduler";
19static constexpr uint32_t MAX_LOGICALLY_DELETED_ITEMS = 5;
21static constexpr uint32_t MAX_INTERVAL_DELAY = 5000;
23#if defined(ESPHOME_LOG_HAS_VERBOSE) || defined(ESPHOME_DEBUG_SCHEDULER)
27struct SchedulerNameLog {
34 const char *
format(Scheduler::NameType name_type,
const char *static_name,
uint32_t hash_or_id) {
35 using NameType = Scheduler::NameType;
36 if (name_type == NameType::STATIC_STRING) {
40 ESPHOME_strncpy_P(buffer, ESPHOME_PSTR(
"(null)"),
sizeof(buffer));
42 }
else if (name_type == NameType::HASHED_STRING) {
43 ESPHOME_snprintf_P(buffer,
sizeof(buffer), ESPHOME_PSTR(
"hash:0x%08" PRIX32), hash_or_id);
45 }
else if (name_type == NameType::NUMERIC_ID) {
46 ESPHOME_snprintf_P(buffer,
sizeof(buffer), ESPHOME_PSTR(
"id:%" PRIu32), hash_or_id);
48 }
else if (name_type == NameType::NUMERIC_ID_INTERNAL) {
49 ESPHOME_snprintf_P(buffer,
sizeof(buffer), ESPHOME_PSTR(
"iid:%" PRIu32), hash_or_id);
54 ESPHOME_snprintf_P(buffer,
sizeof(buffer), ESPHOME_PSTR(
"self:%p"),
55 const_cast<void *
>(
static_cast<const void *
>(static_name)));
65#ifdef ESPHOME_DEBUG_SCHEDULER
67static void validate_static_string(
const char *name) {
73 uintptr_t addr =
reinterpret_cast<uintptr_t
>(
name);
77 uintptr_t stack_addr =
reinterpret_cast<uintptr_t
>(&stack_var);
81 if (addr > (stack_addr - 0x2000) && addr < (stack_addr + 0x2000)) {
83 "WARNING: Scheduler name '%s' at %p appears to be on the stack - this is unsafe!\n"
84 " Stack reference at %p",
85 name, name, &stack_var);
90 static const char *static_str =
"test";
91 uintptr_t static_addr =
reinterpret_cast<uintptr_t
>(static_str);
94 if (addr > static_addr + 0x100000 || (static_addr > 0x100000 && addr < static_addr - 0x100000)) {
95 ESP_LOGW(TAG,
"WARNING: Scheduler name '%s' at %p might be on heap (static ref at %p)", name, name, static_str);
108 uint32_t max_offset = std::min(
delay / 2, MAX_INTERVAL_DELAY);
115void HOT Scheduler::set_timer_common_(Component *
component, SchedulerItem::Type
type, NameType name_type,
117 std::function<
void()> &&func,
bool skip_cancel,
const LogString *source) {
121 LockGuard guard{this->lock_};
122 this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type,
true);
134 if (
type == SchedulerItem::INTERVAL &&
delay == 0) [[unlikely]] {
135 ESP_LOGE(TAG,
"[%s] set_interval(0) would spin main loop - coercing to 1ms (use HighFrequencyLoopRequester)",
141 LockGuard guard{this->lock_};
144 SchedulerItem *item = this->get_item_from_pool_locked_();
146 if (name_type == NameType::SELF_POINTER) {
147 item->source_name = source;
151 item->set_name(name_type, static_name, hash_or_id);
158 item->callback.~function();
159 new (&item->callback) std::function<
void()>(std::move(func));
161 this->set_item_removed_(item,
false);
165 auto *target = &this->to_add_;
167#ifndef ESPHOME_THREAD_SINGLE
170 if (
delay == 0 &&
type == SchedulerItem::TIMEOUT) {
172 target = &this->defer_queue_;
180 if (
type == SchedulerItem::INTERVAL) {
181 item->interval =
delay;
184 item->set_next_execution(now_64 + offset);
185#ifdef ESPHOME_LOG_HAS_VERBOSE
186 SchedulerNameLog name_log;
187 ESP_LOGV(TAG,
"Scheduler interval for %s is %" PRIu32
"ms, offset %" PRIu32
"ms",
188 name_log.format(name_type, static_name, hash_or_id),
delay, offset);
192 item->set_next_execution(now_64 +
delay);
195#ifdef ESPHOME_DEBUG_SCHEDULER
196 this->debug_log_timer_(item, name_type, static_name, hash_or_id,
delay, now_64);
202 if (!skip_cancel && (name_type != NameType::STATIC_STRING || static_name !=
nullptr)) {
203 this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type,
true);
205 target->push_back(item);
206 if (target == &this->to_add_) {
207 this->to_add_count_increment_locked_();
209#ifndef ESPHOME_THREAD_SINGLE
211 this->defer_count_increment_locked_();
216void HOT Scheduler::set_timeout(Component *
component,
const char *name,
uint32_t timeout,
217 std::function<
void()> &&func) {
218 this->set_timer_common_(
component, SchedulerItem::TIMEOUT, NameType::STATIC_STRING, name, 0, timeout,
223 this->set_timer_common_(
component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID,
nullptr,
id, timeout,
226bool HOT Scheduler::cancel_timeout(Component *
component,
const char *name) {
227 return this->cancel_item_(
component, NameType::STATIC_STRING, name, 0, SchedulerItem::TIMEOUT);
230 return this->cancel_item_(
component, NameType::NUMERIC_ID,
nullptr,
id, SchedulerItem::TIMEOUT);
232void HOT Scheduler::set_interval(Component *
component,
const char *name,
uint32_t interval,
233 std::function<
void()> &&func) {
234 this->set_timer_common_(
component, SchedulerItem::INTERVAL, NameType::STATIC_STRING, name, 0, interval,
238 this->set_timer_common_(
component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID,
nullptr,
id, interval,
241bool HOT Scheduler::cancel_interval(Component *
component,
const char *name) {
242 return this->cancel_item_(
component, NameType::STATIC_STRING, name, 0, SchedulerItem::INTERVAL);
245 return this->cancel_item_(
component, NameType::NUMERIC_ID,
nullptr,
id, SchedulerItem::INTERVAL);
252void HOT Scheduler::set_timeout(
const void *self,
uint32_t timeout, std::function<
void()> &&func) {
253 this->set_timer_common_(
nullptr, SchedulerItem::TIMEOUT, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
254 timeout, std::move(func));
256void HOT Scheduler::set_interval(
const void *self,
uint32_t interval, std::function<
void()> &&func) {
257 this->set_timer_common_(
nullptr, SchedulerItem::INTERVAL, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
258 interval, std::move(func));
260bool HOT Scheduler::cancel_timeout(
const void *self) {
261 return this->cancel_item_(
nullptr, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
262 SchedulerItem::TIMEOUT);
264bool HOT Scheduler::cancel_interval(
const void *self) {
265 return this->cancel_item_(
nullptr, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
266 SchedulerItem::INTERVAL);
269optional<uint32_t> HOT Scheduler::next_schedule_in(
uint32_t now) {
279#ifndef ESPHOME_THREAD_SINGLE
283 if (!this->defer_empty_())
289 if (!this->to_add_empty_())
294 if (!this->cleanup_())
297 SchedulerItem *item = this->items_[0];
298 const auto now_64 = this->millis_64_from_(now);
299 const uint64_t next_exec = item->get_next_execution();
300 if (next_exec < now_64)
302 return next_exec - now_64;
305void Scheduler::full_cleanup_removed_items_() {
311 LockGuard guard{this->lock_};
315 for (
size_t read = 0; read < this->items_.size(); ++read) {
316 if (!is_item_removed_locked_(this->items_[read])) {
318 this->items_[write] = this->items_[read];
322 this->recycle_item_main_loop_(this->items_[read]);
325 this->items_.erase(this->items_.begin() + write, this->items_.end());
327 std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
328 this->to_remove_clear_locked_();
331#ifndef ESPHOME_THREAD_SINGLE
332void Scheduler::compact_defer_queue_locked_() {
344 size_t remaining = this->defer_queue_.size() - this->defer_queue_front_;
345 for (
size_t i = 0; i < remaining; i++) {
346 this->defer_queue_[i] = this->defer_queue_[this->defer_queue_front_ + i];
350 this->defer_queue_.erase(this->defer_queue_.begin() + remaining, this->defer_queue_.end());
352void HOT Scheduler::process_defer_queue_slow_path_(
uint32_t &now) {
377 this->defer_count_clear_locked_();
378 size_t defer_queue_end = this->defer_queue_.size();
379 if (this->defer_queue_front_ >= defer_queue_end) {
380 this->lock_.unlock();
383 while (this->defer_queue_front_ < defer_queue_end) {
389 item = this->defer_queue_[this->defer_queue_front_];
390 this->defer_queue_[this->defer_queue_front_] =
nullptr;
391 this->defer_queue_front_++;
392 this->lock_.unlock();
396 if (!this->should_skip_item_(item)) {
397 now = this->execute_item_(item, now);
401 this->recycle_item_main_loop_(item);
404 this->cleanup_defer_queue_locked_();
405 this->lock_.unlock();
410#ifndef ESPHOME_THREAD_SINGLE
411 this->process_defer_queue_(now);
415 const auto now_64 = this->millis_64_from_(now);
416 this->process_to_add();
419 bool has_added_items =
false;
421#ifdef ESPHOME_DEBUG_SCHEDULER
422 static uint64_t last_print = 0;
424 if (now_64 - last_print > 2000) {
426 std::vector<SchedulerItem *> old_items;
427 ESP_LOGD(TAG,
"Items: count=%zu, pool=%zu, now=%" PRIu64, this->items_.size(), this->scheduler_item_pool_size_,
431 while (!this->items_.empty()) {
434 LockGuard guard{this->lock_};
435 item = this->pop_raw_locked_();
438 SchedulerNameLog name_log;
439 bool is_cancelled = is_item_removed_(item);
440 ESP_LOGD(TAG,
" %s '%s/%s' interval=%" PRIu32
" next_execution in %" PRIu64
"ms at %" PRIu64
"%s",
441 LOG_STR_ARG(item->get_type_str()), LOG_STR_ARG(item->get_source()),
442 name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval,
443 item->get_next_execution() - now_64, item->get_next_execution(),
444 is_cancelled ? LOG_STR_LITERAL(
" [CANCELLED]") : LOG_STR_LITERAL(
""));
446 old_items.push_back(item);
451 LockGuard guard{this->lock_};
452 this->items_ = std::move(old_items);
454 std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
465 if (this->to_remove_count_() >= MAX_LOGICALLY_DELETED_ITEMS) {
466 this->full_cleanup_removed_items_();
474 while (!this->items_.empty()) {
476 SchedulerItem *item = this->items_[0];
477 if (item->get_next_execution() > now_64) {
482 if (this->is_item_failed_(item)) {
483 LockGuard guard{this->lock_};
484 this->recycle_item_main_loop_(this->pop_raw_locked_());
492#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
495 LockGuard guard{this->lock_};
496 if (is_item_removed_locked_(item)) {
497 this->recycle_item_main_loop_(this->pop_raw_locked_());
498 this->to_remove_decrement_locked_();
504 if (is_item_removed_(item)) {
505 LockGuard guard{this->lock_};
506 this->recycle_item_main_loop_(this->pop_raw_locked_());
507 this->to_remove_decrement_locked_();
512#ifdef ESPHOME_DEBUG_SCHEDULER
514 SchedulerNameLog name_log;
515 ESP_LOGV(TAG,
"Running %s '%s/%s' with interval=%" PRIu32
" next_execution=%" PRIu64
" (now=%" PRIu64
")",
516 LOG_STR_ARG(item->get_type_str()), LOG_STR_ARG(item->get_source()),
517 name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval,
518 item->get_next_execution(), now_64);
525 now = this->execute_item_(item, now);
527 LockGuard guard{this->lock_};
531 SchedulerItem *executed_item = this->pop_raw_locked_();
533 if (this->is_item_removed_locked_(executed_item)) {
535 this->to_remove_decrement_locked_();
536 this->recycle_item_main_loop_(executed_item);
540 if (executed_item->type == SchedulerItem::INTERVAL) {
541 executed_item->set_next_execution(now_64 + executed_item->interval);
552 this->items_.push_back(executed_item);
553 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
556 this->recycle_item_main_loop_(executed_item);
559 has_added_items |= !this->to_add_.empty();
562 if (has_added_items) {
563 this->process_to_add();
566#ifdef ESPHOME_DEBUG_SCHEDULER
576 LockGuard guard{this->lock_};
577 this->debug_verify_no_leak_();
584void HOT Scheduler::process_to_add_slow_path_() {
585 LockGuard guard{this->lock_};
586 for (
auto *&it : this->to_add_) {
587 if (is_item_removed_locked_(it)) {
589 this->recycle_item_main_loop_(it);
594 this->items_.push_back(it);
595 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
597 this->to_add_.clear();
598 this->to_add_count_clear_locked_();
600bool HOT Scheduler::cleanup_slow_path_() {
609 LockGuard guard{this->lock_};
610 while (!this->items_.empty()) {
611 SchedulerItem *item = this->items_[0];
612 if (!this->is_item_removed_locked_(item))
614 this->to_remove_decrement_locked_();
615 this->recycle_item_main_loop_(this->pop_raw_locked_());
617 return !this->items_.empty();
619Scheduler::SchedulerItem *HOT Scheduler::pop_raw_locked_() {
620 std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
622 SchedulerItem *item = this->items_.back();
623 this->items_.pop_back();
634 const LogString *source;
635 if (item->get_name_type() == NameType::SELF_POINTER) {
637 source = item->source_name;
643 LoopBlockingGuard guard{
component, source, now};
655bool HOT Scheduler::cancel_item_(Component *
component, NameType name_type,
const char *static_name,
uint32_t hash_or_id,
656 SchedulerItem::Type
type) {
657 LockGuard guard{this->lock_};
660 return this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type);
669size_t Scheduler::mark_matching_items_removed_slow_locked_(std::vector<SchedulerItem *> &container,
670 Component *
component, NameType name_type,
671 const char *static_name,
uint32_t hash_or_id,
672 SchedulerItem::Type
type,
bool find_first) {
674 for (
auto *item : container) {
675 if (this->matches_item_locked_(item,
component, name_type, static_name, hash_or_id,
type)) {
676 this->set_item_removed_(item,
true);
685bool HOT Scheduler::cancel_item_locked_(Component *
component, NameType name_type,
const char *static_name,
686 uint32_t hash_or_id, SchedulerItem::Type
type,
bool find_first) {
688 if (name_type == NameType::STATIC_STRING && static_name ==
nullptr) {
692 size_t total_cancelled = 0;
694#ifndef ESPHOME_THREAD_SINGLE
696 if (
type == SchedulerItem::TIMEOUT) {
697 total_cancelled += this->mark_matching_items_removed_locked_(this->defer_queue_,
component, name_type, static_name,
698 hash_or_id,
type, find_first);
699 if (find_first && total_cancelled > 0)
710 size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_,
component, name_type, static_name,
711 hash_or_id,
type, find_first);
712 total_cancelled += heap_cancelled;
713 this->to_remove_add_locked_(heap_cancelled);
714 if (find_first && total_cancelled > 0)
719 total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_,
component, name_type, static_name,
720 hash_or_id,
type, find_first);
722 return total_cancelled > 0;
725bool HOT Scheduler::SchedulerItem::cmp(SchedulerItem *a, SchedulerItem *b) {
728 return (a->next_execution_high_ ==
b->next_execution_high_) ? (a->next_execution_low_ >
b->next_execution_low_)
729 : (a->next_execution_high_ >
b->next_execution_high_);
734void Scheduler::recycle_item_main_loop_(SchedulerItem *item) {
738 item->callback =
nullptr;
739 item->next_free = this->scheduler_item_pool_head_;
740 this->scheduler_item_pool_head_ = item;
741 this->scheduler_item_pool_size_++;
742#ifdef ESPHOME_DEBUG_SCHEDULER
743 ESP_LOGD(TAG,
"Recycled item to pool (pool size now: %zu)", this->scheduler_item_pool_size_);
755void __attribute__((noinline)) Scheduler::shrink_scheduler_vector_(std::vector<SchedulerItem *> *v) {
756 if (v->capacity() == v->size())
758 std::vector<SchedulerItem *> tmp;
759 tmp.reserve(v->size());
760 for (SchedulerItem *p : *v)
765void Scheduler::trim_freelist() {
766 LockGuard guard{this->lock_};
767 SchedulerItem *item = this->scheduler_item_pool_head_;
769 while (item !=
nullptr) {
770 SchedulerItem *next = item->next_free;
772#ifdef ESPHOME_DEBUG_SCHEDULER
773 this->debug_live_items_--;
778 this->scheduler_item_pool_head_ =
nullptr;
779 this->scheduler_item_pool_size_ = 0;
783 shrink_scheduler_vector_(&this->items_);
784 shrink_scheduler_vector_(&this->to_add_);
785#ifndef ESPHOME_THREAD_SINGLE
786 shrink_scheduler_vector_(&this->defer_queue_);
789#ifdef ESPHOME_DEBUG_SCHEDULER
790 ESP_LOGD(TAG,
"Freelist trimmed (%zu items freed)", freed);
796#ifdef ESPHOME_DEBUG_SCHEDULER
797void Scheduler::debug_log_timer_(
const SchedulerItem *item, NameType name_type,
const char *static_name,
800 if (name_type == NameType::STATIC_STRING && static_name !=
nullptr) {
801 validate_static_string(static_name);
805 SchedulerNameLog name_log;
806 const char *type_str = LOG_STR_ARG(item->get_type_str());
807 if (item->type == SchedulerItem::TIMEOUT) {
808 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
809 name_log.format(name_type, static_name, hash_or_id), type_str,
delay);
811 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
", offset=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
812 name_log.format(name_type, static_name, hash_or_id), type_str,
delay,
813 static_cast<uint32_t>(item->get_next_execution() - now));
820Scheduler::SchedulerItem *Scheduler::get_item_from_pool_locked_() {
821 if (this->scheduler_item_pool_head_ !=
nullptr) {
822 SchedulerItem *item = this->scheduler_item_pool_head_;
823 this->scheduler_item_pool_head_ = item->next_free;
824 this->scheduler_item_pool_size_--;
825#ifdef ESPHOME_DEBUG_SCHEDULER
826 ESP_LOGD(TAG,
"Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_size_);
830#ifdef ESPHOME_DEBUG_SCHEDULER
831 ESP_LOGD(TAG,
"Allocated new item (pool empty)");
833 auto *item =
new SchedulerItem();
834#ifdef ESPHOME_DEBUG_SCHEDULER
835 this->debug_live_items_++;
840#ifdef ESPHOME_DEBUG_SCHEDULER
841bool Scheduler::debug_verify_no_leak_()
const {
844 size_t accounted = this->items_.size() + this->to_add_.size() + this->scheduler_item_pool_size_;
845#ifndef ESPHOME_THREAD_SINGLE
846 accounted += this->defer_queue_.size();
848 if (accounted != this->debug_live_items_) {
850 "SCHEDULER LEAK DETECTED: live=%" PRIu32
" but accounted=%" PRIu32
" (items=%" PRIu32
" to_add=%" PRIu32
852#ifndef ESPHOME_THREAD_SINGLE
856 static_cast<uint32_t>(this->debug_live_items_),
static_cast<uint32_t>(accounted),
857 static_cast<uint32_t>(this->items_.size()),
static_cast<uint32_t>(this->to_add_.size()),
858 static_cast<uint32_t>(this->scheduler_item_pool_size_)
859#ifndef ESPHOME_THREAD_SINGLE
861 static_cast<uint32_t>(this->defer_queue_.size())
void ESPHOME_ALWAYS_INLINE feed_wdt_with_time(uint32_t time)
Feed the task watchdog, hot entry.
const LogString * get_component_log_str() const ESPHOME_ALWAYS_INLINE
Get the integration where this component was declared as a LogString for logging.
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
const Component * component
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
void HOT delay(uint32_t ms)
Application App
Global storage of Application pointer - only one Application can exist.
constexpr uint32_t SCHEDULER_DONT_RUN