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,
type,
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 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(), is_cancelled ?
" [CANCELLED]" :
"");
445 old_items.push_back(item);
450 LockGuard guard{this->lock_};
451 this->items_ = std::move(old_items);
453 std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
464 if (this->to_remove_count_() >= MAX_LOGICALLY_DELETED_ITEMS) {
465 this->full_cleanup_removed_items_();
473 while (!this->items_.empty()) {
475 SchedulerItem *item = this->items_[0];
476 if (item->get_next_execution() > now_64) {
481 if (this->is_item_failed_(item)) {
482 LockGuard guard{this->lock_};
483 this->recycle_item_main_loop_(this->pop_raw_locked_());
491#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
494 LockGuard guard{this->lock_};
495 if (is_item_removed_locked_(item)) {
496 this->recycle_item_main_loop_(this->pop_raw_locked_());
497 this->to_remove_decrement_locked_();
503 if (is_item_removed_(item)) {
504 LockGuard guard{this->lock_};
505 this->recycle_item_main_loop_(this->pop_raw_locked_());
506 this->to_remove_decrement_locked_();
511#ifdef ESPHOME_DEBUG_SCHEDULER
513 SchedulerNameLog name_log;
514 ESP_LOGV(TAG,
"Running %s '%s/%s' with interval=%" PRIu32
" next_execution=%" PRIu64
" (now=%" PRIu64
")",
515 item->get_type_str(), LOG_STR_ARG(item->get_source()),
516 name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval,
517 item->get_next_execution(), now_64);
524 now = this->execute_item_(item, now);
526 LockGuard guard{this->lock_};
530 SchedulerItem *executed_item = this->pop_raw_locked_();
532 if (this->is_item_removed_locked_(executed_item)) {
534 this->to_remove_decrement_locked_();
535 this->recycle_item_main_loop_(executed_item);
539 if (executed_item->type == SchedulerItem::INTERVAL) {
540 executed_item->set_next_execution(now_64 + executed_item->interval);
551 this->items_.push_back(executed_item);
552 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
555 this->recycle_item_main_loop_(executed_item);
558 has_added_items |= !this->to_add_.empty();
561 if (has_added_items) {
562 this->process_to_add();
565#ifdef ESPHOME_DEBUG_SCHEDULER
575 LockGuard guard{this->lock_};
576 this->debug_verify_no_leak_();
583void HOT Scheduler::process_to_add_slow_path_() {
584 LockGuard guard{this->lock_};
585 for (
auto *&it : this->to_add_) {
586 if (is_item_removed_locked_(it)) {
588 this->recycle_item_main_loop_(it);
593 this->items_.push_back(it);
594 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
596 this->to_add_.clear();
597 this->to_add_count_clear_locked_();
599bool HOT Scheduler::cleanup_slow_path_() {
608 LockGuard guard{this->lock_};
609 while (!this->items_.empty()) {
610 SchedulerItem *item = this->items_[0];
611 if (!this->is_item_removed_locked_(item))
613 this->to_remove_decrement_locked_();
614 this->recycle_item_main_loop_(this->pop_raw_locked_());
616 return !this->items_.empty();
618Scheduler::SchedulerItem *HOT Scheduler::pop_raw_locked_() {
619 std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
621 SchedulerItem *item = this->items_.back();
622 this->items_.pop_back();
633 const LogString *source;
634 if (item->get_name_type() == NameType::SELF_POINTER) {
636 source = item->source_name;
642 LoopBlockingGuard guard{
component, source, now};
654bool HOT Scheduler::cancel_item_(Component *
component, NameType name_type,
const char *static_name,
uint32_t hash_or_id,
655 SchedulerItem::Type
type) {
656 LockGuard guard{this->lock_};
659 return this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type);
668size_t Scheduler::mark_matching_items_removed_slow_locked_(std::vector<SchedulerItem *> &container,
669 Component *
component, NameType name_type,
670 const char *static_name,
uint32_t hash_or_id,
671 SchedulerItem::Type
type,
bool find_first) {
673 for (
auto *item : container) {
674 if (this->matches_item_locked_(item,
component, name_type, static_name, hash_or_id,
type)) {
675 this->set_item_removed_(item,
true);
684bool HOT Scheduler::cancel_item_locked_(Component *
component, NameType name_type,
const char *static_name,
685 uint32_t hash_or_id, SchedulerItem::Type
type,
bool find_first) {
687 if (name_type == NameType::STATIC_STRING && static_name ==
nullptr) {
691 size_t total_cancelled = 0;
693#ifndef ESPHOME_THREAD_SINGLE
695 if (
type == SchedulerItem::TIMEOUT) {
696 total_cancelled += this->mark_matching_items_removed_locked_(this->defer_queue_,
component, name_type, static_name,
697 hash_or_id,
type, find_first);
698 if (find_first && total_cancelled > 0)
709 size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_,
component, name_type, static_name,
710 hash_or_id,
type, find_first);
711 total_cancelled += heap_cancelled;
712 this->to_remove_add_locked_(heap_cancelled);
713 if (find_first && total_cancelled > 0)
718 total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_,
component, name_type, static_name,
719 hash_or_id,
type, find_first);
721 return total_cancelled > 0;
724bool HOT Scheduler::SchedulerItem::cmp(SchedulerItem *a, SchedulerItem *b) {
727 return (a->next_execution_high_ ==
b->next_execution_high_) ? (a->next_execution_low_ >
b->next_execution_low_)
728 : (a->next_execution_high_ >
b->next_execution_high_);
733void Scheduler::recycle_item_main_loop_(SchedulerItem *item) {
737 item->callback =
nullptr;
738 item->next_free = this->scheduler_item_pool_head_;
739 this->scheduler_item_pool_head_ = item;
740 this->scheduler_item_pool_size_++;
741#ifdef ESPHOME_DEBUG_SCHEDULER
742 ESP_LOGD(TAG,
"Recycled item to pool (pool size now: %zu)", this->scheduler_item_pool_size_);
754void __attribute__((noinline)) Scheduler::shrink_scheduler_vector_(std::vector<SchedulerItem *> *v) {
755 if (v->capacity() == v->size())
757 std::vector<SchedulerItem *> tmp;
758 tmp.reserve(v->size());
759 for (SchedulerItem *p : *v)
764void Scheduler::trim_freelist() {
765 LockGuard guard{this->lock_};
766 SchedulerItem *item = this->scheduler_item_pool_head_;
768 while (item !=
nullptr) {
769 SchedulerItem *next = item->next_free;
771#ifdef ESPHOME_DEBUG_SCHEDULER
772 this->debug_live_items_--;
777 this->scheduler_item_pool_head_ =
nullptr;
778 this->scheduler_item_pool_size_ = 0;
782 shrink_scheduler_vector_(&this->items_);
783 shrink_scheduler_vector_(&this->to_add_);
784#ifndef ESPHOME_THREAD_SINGLE
785 shrink_scheduler_vector_(&this->defer_queue_);
788#ifdef ESPHOME_DEBUG_SCHEDULER
789 ESP_LOGD(TAG,
"Freelist trimmed (%zu items freed)", freed);
795#ifdef ESPHOME_DEBUG_SCHEDULER
796void Scheduler::debug_log_timer_(
const SchedulerItem *item, NameType name_type,
const char *static_name,
799 if (name_type == NameType::STATIC_STRING && static_name !=
nullptr) {
800 validate_static_string(static_name);
804 SchedulerNameLog name_log;
805 const char *type_str = (
type == SchedulerItem::TIMEOUT) ?
"timeout" :
"interval";
806 if (
type == SchedulerItem::TIMEOUT) {
807 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
808 name_log.format(name_type, static_name, hash_or_id), type_str,
delay);
810 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
", offset=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
811 name_log.format(name_type, static_name, hash_or_id), type_str,
delay,
812 static_cast<uint32_t>(item->get_next_execution() - now));
819Scheduler::SchedulerItem *Scheduler::get_item_from_pool_locked_() {
820 if (this->scheduler_item_pool_head_ !=
nullptr) {
821 SchedulerItem *item = this->scheduler_item_pool_head_;
822 this->scheduler_item_pool_head_ = item->next_free;
823 this->scheduler_item_pool_size_--;
824#ifdef ESPHOME_DEBUG_SCHEDULER
825 ESP_LOGD(TAG,
"Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_size_);
829#ifdef ESPHOME_DEBUG_SCHEDULER
830 ESP_LOGD(TAG,
"Allocated new item (pool empty)");
832 auto *item =
new SchedulerItem();
833#ifdef ESPHOME_DEBUG_SCHEDULER
834 this->debug_live_items_++;
839#ifdef ESPHOME_DEBUG_SCHEDULER
840bool Scheduler::debug_verify_no_leak_()
const {
843 size_t accounted = this->items_.size() + this->to_add_.size() + this->scheduler_item_pool_size_;
844#ifndef ESPHOME_THREAD_SINGLE
845 accounted += this->defer_queue_.size();
847 if (accounted != this->debug_live_items_) {
849 "SCHEDULER LEAK DETECTED: live=%" PRIu32
" but accounted=%" PRIu32
" (items=%" PRIu32
" to_add=%" PRIu32
851#ifndef ESPHOME_THREAD_SINGLE
855 static_cast<uint32_t>(this->debug_live_items_),
static_cast<uint32_t>(accounted),
856 static_cast<uint32_t>(this->items_.size()),
static_cast<uint32_t>(this->to_add_.size()),
857 static_cast<uint32_t>(this->scheduler_item_pool_size_)
858#ifndef ESPHOME_THREAD_SINGLE
860 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
const char int const __FlashStringHelper * format
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