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)",
142 LockGuard guard{this->lock_};
145 SchedulerItem *item = this->get_item_from_pool_locked_();
147 if (name_type == NameType::SELF_POINTER) {
148 item->source_name = source;
152 item->set_name(name_type, static_name, hash_or_id);
159 item->callback.~function();
160 new (&item->callback) std::function<
void()>(std::move(func));
162 this->set_item_removed_(item,
false);
166 auto *target = &this->to_add_;
168#ifndef ESPHOME_THREAD_SINGLE
171 if (
delay == 0 &&
type == SchedulerItem::TIMEOUT) {
173 target = &this->defer_queue_;
181 if (
type == SchedulerItem::INTERVAL) {
182 item->interval =
delay;
185 item->set_next_execution(now_64 + offset);
186#ifdef ESPHOME_LOG_HAS_VERBOSE
187 SchedulerNameLog name_log;
188 ESP_LOGV(TAG,
"Scheduler interval for %s is %" PRIu32
"ms, offset %" PRIu32
"ms",
189 name_log.format(name_type, static_name, hash_or_id),
delay, offset);
193 item->set_next_execution(now_64 +
delay);
196#ifdef ESPHOME_DEBUG_SCHEDULER
197 this->debug_log_timer_(item, name_type, static_name, hash_or_id,
delay, now_64);
203 if (!skip_cancel && (name_type != NameType::STATIC_STRING || static_name !=
nullptr)) {
204 this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type,
true);
206 target->push_back(item);
207 if (target == &this->to_add_) {
208 this->to_add_count_increment_locked_();
210#ifndef ESPHOME_THREAD_SINGLE
212 this->defer_count_increment_locked_();
217 if (!is_main_loop_thread()) [[unlikely]]
221void HOT Scheduler::set_timeout(Component *
component,
const char *name,
uint32_t timeout,
222 std::function<
void()> &&func) {
223 this->set_timer_common_(
component, SchedulerItem::TIMEOUT, NameType::STATIC_STRING, name, 0, timeout,
228 this->set_timer_common_(
component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID,
nullptr,
id, timeout,
231bool HOT Scheduler::cancel_timeout(Component *
component,
const char *name) {
232 return this->cancel_item_(
component, NameType::STATIC_STRING, name, 0, SchedulerItem::TIMEOUT);
235 return this->cancel_item_(
component, NameType::NUMERIC_ID,
nullptr,
id, SchedulerItem::TIMEOUT);
237void HOT Scheduler::set_interval(Component *
component,
const char *name,
uint32_t interval,
238 std::function<
void()> &&func) {
239 this->set_timer_common_(
component, SchedulerItem::INTERVAL, NameType::STATIC_STRING, name, 0, interval,
243 this->set_timer_common_(
component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID,
nullptr,
id, interval,
246bool HOT Scheduler::cancel_interval(Component *
component,
const char *name) {
247 return this->cancel_item_(
component, NameType::STATIC_STRING, name, 0, SchedulerItem::INTERVAL);
250 return this->cancel_item_(
component, NameType::NUMERIC_ID,
nullptr,
id, SchedulerItem::INTERVAL);
257void HOT Scheduler::set_timeout(
const void *self,
uint32_t timeout, std::function<
void()> &&func) {
258 this->set_timer_common_(
nullptr, SchedulerItem::TIMEOUT, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
259 timeout, std::move(func));
261void HOT Scheduler::set_interval(
const void *self,
uint32_t interval, std::function<
void()> &&func) {
262 this->set_timer_common_(
nullptr, SchedulerItem::INTERVAL, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
263 interval, std::move(func));
265bool HOT Scheduler::cancel_timeout(
const void *self) {
266 return this->cancel_item_(
nullptr, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
267 SchedulerItem::TIMEOUT);
269bool HOT Scheduler::cancel_interval(
const void *self) {
270 return this->cancel_item_(
nullptr, NameType::SELF_POINTER,
static_cast<const char *
>(self), 0,
271 SchedulerItem::INTERVAL);
274optional<uint32_t> HOT Scheduler::next_schedule_in(
uint32_t now) {
284#ifndef ESPHOME_THREAD_SINGLE
288 if (!this->defer_empty_())
294 if (!this->to_add_empty_())
299 if (!this->cleanup_())
302 SchedulerItem *item = this->items_[0];
303 const auto now_64 = this->millis_64_from_(now);
304 const uint64_t next_exec = item->get_next_execution();
305 if (next_exec < now_64)
307 return next_exec - now_64;
310void Scheduler::full_cleanup_removed_items_() {
316 LockGuard guard{this->lock_};
320 for (
size_t read = 0; read < this->items_.size(); ++read) {
321 if (!is_item_removed_locked_(this->items_[read])) {
323 this->items_[write] = this->items_[read];
327 this->recycle_item_main_loop_(this->items_[read]);
330 this->items_.erase(this->items_.begin() + write, this->items_.end());
332 std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
333 this->to_remove_clear_locked_();
336#ifndef ESPHOME_THREAD_SINGLE
337void Scheduler::compact_defer_queue_locked_() {
349 size_t remaining = this->defer_queue_.size() - this->defer_queue_front_;
350 for (
size_t i = 0; i < remaining; i++) {
351 this->defer_queue_[i] = this->defer_queue_[this->defer_queue_front_ + i];
355 this->defer_queue_.erase(this->defer_queue_.begin() + remaining, this->defer_queue_.end());
357void HOT Scheduler::process_defer_queue_slow_path_(
uint32_t &now) {
382 this->defer_count_clear_locked_();
383 size_t defer_queue_end = this->defer_queue_.size();
384 if (this->defer_queue_front_ >= defer_queue_end) {
385 this->lock_.unlock();
388 while (this->defer_queue_front_ < defer_queue_end) {
394 item = this->defer_queue_[this->defer_queue_front_];
395 this->defer_queue_[this->defer_queue_front_] =
nullptr;
396 this->defer_queue_front_++;
397 this->lock_.unlock();
401 if (!this->should_skip_item_(item)) {
402 now = this->execute_item_(item, now);
406 this->recycle_item_main_loop_(item);
409 this->cleanup_defer_queue_locked_();
410 this->lock_.unlock();
415#ifndef ESPHOME_THREAD_SINGLE
416 this->process_defer_queue_(now);
420 const auto now_64 = this->millis_64_from_(now);
421 this->process_to_add();
424 bool has_added_items =
false;
426#ifdef ESPHOME_DEBUG_SCHEDULER
427 static uint64_t last_print = 0;
429 if (now_64 - last_print > 2000) {
431 std::vector<SchedulerItem *> old_items;
432 ESP_LOGD(TAG,
"Items: count=%zu, pool=%zu, now=%" PRIu64, this->items_.size(), this->scheduler_item_pool_size_,
436 while (!this->items_.empty()) {
439 LockGuard guard{this->lock_};
440 item = this->pop_raw_locked_();
443 SchedulerNameLog name_log;
444 bool is_cancelled = is_item_removed_(item);
445 ESP_LOGD(TAG,
" %s '%s/%s' interval=%" PRIu32
" next_execution in %" PRId64
"ms at %" PRIu64
"%s",
446 LOG_STR_ARG(item->get_type_str()), LOG_STR_ARG(item->get_source()),
447 name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval,
448 (int64_t) (item->get_next_execution() - now_64), item->get_next_execution(),
449 is_cancelled ? LOG_STR_LITERAL(
" [CANCELLED]") : LOG_STR_LITERAL(
""));
451 old_items.push_back(item);
456 LockGuard guard{this->lock_};
457 this->items_ = std::move(old_items);
459 std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
470 if (this->to_remove_count_() >= MAX_LOGICALLY_DELETED_ITEMS) {
471 this->full_cleanup_removed_items_();
479 while (!this->items_.empty()) {
481 SchedulerItem *item = this->items_[0];
482 if (item->get_next_execution() > now_64) {
487 if (this->is_item_failed_(item)) {
488 LockGuard guard{this->lock_};
489 this->recycle_item_main_loop_(this->pop_raw_locked_());
497#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
500 LockGuard guard{this->lock_};
501 if (is_item_removed_locked_(item)) {
502 this->recycle_item_main_loop_(this->pop_raw_locked_());
503 this->to_remove_decrement_locked_();
509 if (is_item_removed_(item)) {
510 LockGuard guard{this->lock_};
511 this->recycle_item_main_loop_(this->pop_raw_locked_());
512 this->to_remove_decrement_locked_();
517#ifdef ESPHOME_DEBUG_SCHEDULER
519 SchedulerNameLog name_log;
520 ESP_LOGV(TAG,
"Running %s '%s/%s' with interval=%" PRIu32
" next_execution=%" PRIu64
" (now=%" PRIu64
")",
521 LOG_STR_ARG(item->get_type_str()), LOG_STR_ARG(item->get_source()),
522 name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval,
523 item->get_next_execution(), now_64);
530 now = this->execute_item_(item, now);
532 LockGuard guard{this->lock_};
536 SchedulerItem *executed_item = this->pop_raw_locked_();
538 if (this->is_item_removed_locked_(executed_item)) {
540 this->to_remove_decrement_locked_();
541 this->recycle_item_main_loop_(executed_item);
542 has_added_items |= !this->to_add_.empty();
546 if (executed_item->type == SchedulerItem::INTERVAL) {
547 executed_item->set_next_execution(now_64 + executed_item->interval);
558 this->items_.push_back(executed_item);
559 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
562 this->recycle_item_main_loop_(executed_item);
565 has_added_items |= !this->to_add_.empty();
568 if (has_added_items) {
569 this->process_to_add();
572#ifdef ESPHOME_DEBUG_SCHEDULER
582 LockGuard guard{this->lock_};
583 this->debug_verify_no_leak_();
590void HOT Scheduler::process_to_add_slow_path_() {
591 LockGuard guard{this->lock_};
592 for (
auto *&it : this->to_add_) {
593 if (is_item_removed_locked_(it)) {
595 this->recycle_item_main_loop_(it);
600 this->items_.push_back(it);
601 std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
603 this->to_add_.clear();
604 this->to_add_count_clear_locked_();
606bool HOT Scheduler::cleanup_slow_path_() {
615 LockGuard guard{this->lock_};
616 while (!this->items_.empty()) {
617 SchedulerItem *item = this->items_[0];
618 if (!this->is_item_removed_locked_(item))
620 this->to_remove_decrement_locked_();
621 this->recycle_item_main_loop_(this->pop_raw_locked_());
623 return !this->items_.empty();
625Scheduler::SchedulerItem *HOT Scheduler::pop_raw_locked_() {
626 std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
628 SchedulerItem *item = this->items_.back();
629 this->items_.pop_back();
640 const LogString *source;
641 if (item->get_name_type() == NameType::SELF_POINTER) {
643 source = item->source_name;
649 LoopBlockingGuard guard{
component, source, now};
661bool HOT Scheduler::cancel_item_(Component *
component, NameType name_type,
const char *static_name,
uint32_t hash_or_id,
662 SchedulerItem::Type
type) {
663 LockGuard guard{this->lock_};
666 return this->cancel_item_locked_(
component, name_type, static_name, hash_or_id,
type);
675size_t Scheduler::mark_matching_items_removed_slow_locked_(std::vector<SchedulerItem *> &container,
676 Component *
component, NameType name_type,
677 const char *static_name,
uint32_t hash_or_id,
678 SchedulerItem::Type
type,
bool find_first) {
680 for (
auto *item : container) {
681 if (this->matches_item_locked_(item,
component, name_type, static_name, hash_or_id,
type)) {
682 this->set_item_removed_(item,
true);
691bool HOT Scheduler::cancel_item_locked_(Component *
component, NameType name_type,
const char *static_name,
692 uint32_t hash_or_id, SchedulerItem::Type
type,
bool find_first) {
694 if (name_type == NameType::STATIC_STRING && static_name ==
nullptr) {
698 size_t total_cancelled = 0;
700#ifndef ESPHOME_THREAD_SINGLE
702 if (
type == SchedulerItem::TIMEOUT) {
703 total_cancelled += this->mark_matching_items_removed_locked_(this->defer_queue_,
component, name_type, static_name,
704 hash_or_id,
type, find_first);
705 if (find_first && total_cancelled > 0)
716 size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_,
component, name_type, static_name,
717 hash_or_id,
type, find_first);
718 total_cancelled += heap_cancelled;
719 this->to_remove_add_locked_(heap_cancelled);
720 if (find_first && total_cancelled > 0)
725 total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_,
component, name_type, static_name,
726 hash_or_id,
type, find_first);
728 return total_cancelled > 0;
731bool HOT Scheduler::SchedulerItem::cmp(SchedulerItem *a, SchedulerItem *b) {
734 return (a->next_execution_high_ ==
b->next_execution_high_) ? (a->next_execution_low_ >
b->next_execution_low_)
735 : (a->next_execution_high_ >
b->next_execution_high_);
740void Scheduler::recycle_item_main_loop_(SchedulerItem *item) {
744 item->callback =
nullptr;
745 item->next_free = this->scheduler_item_pool_head_;
746 this->scheduler_item_pool_head_ = item;
747 this->scheduler_item_pool_size_++;
748#ifdef ESPHOME_DEBUG_SCHEDULER
749 ESP_LOGD(TAG,
"Recycled item to pool (pool size now: %zu)", this->scheduler_item_pool_size_);
761void __attribute__((noinline)) Scheduler::shrink_scheduler_vector_(std::vector<SchedulerItem *> *v) {
762 if (v->capacity() == v->size())
764 std::vector<SchedulerItem *> tmp;
765 tmp.reserve(v->size());
766 for (SchedulerItem *p : *v)
771void Scheduler::trim_freelist() {
772 LockGuard guard{this->lock_};
773 SchedulerItem *item = this->scheduler_item_pool_head_;
775 while (item !=
nullptr) {
776 SchedulerItem *
next = item->next_free;
778#ifdef ESPHOME_DEBUG_SCHEDULER
779 this->debug_live_items_--;
784 this->scheduler_item_pool_head_ =
nullptr;
785 this->scheduler_item_pool_size_ = 0;
789 shrink_scheduler_vector_(&this->items_);
790 shrink_scheduler_vector_(&this->to_add_);
791#ifndef ESPHOME_THREAD_SINGLE
792 shrink_scheduler_vector_(&this->defer_queue_);
795#ifdef ESPHOME_DEBUG_SCHEDULER
796 ESP_LOGD(TAG,
"Freelist trimmed (%zu items freed)", freed);
802#ifdef ESPHOME_DEBUG_SCHEDULER
803void Scheduler::debug_log_timer_(
const SchedulerItem *item, NameType name_type,
const char *static_name,
806 if (name_type == NameType::STATIC_STRING && static_name !=
nullptr) {
807 validate_static_string(static_name);
811 SchedulerNameLog name_log;
812 const char *type_str = LOG_STR_ARG(item->get_type_str());
813 if (item->type == SchedulerItem::TIMEOUT) {
814 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
815 name_log.format(name_type, static_name, hash_or_id), type_str,
delay);
817 ESP_LOGD(TAG,
"set_%s(name='%s/%s', %s=%" PRIu32
", offset=%" PRIu32
")", type_str, LOG_STR_ARG(item->get_source()),
818 name_log.format(name_type, static_name, hash_or_id), type_str,
delay,
819 static_cast<uint32_t>(item->get_next_execution() - now));
826Scheduler::SchedulerItem *Scheduler::get_item_from_pool_locked_() {
827 if (this->scheduler_item_pool_head_ !=
nullptr) {
828 SchedulerItem *item = this->scheduler_item_pool_head_;
829 this->scheduler_item_pool_head_ = item->next_free;
830 this->scheduler_item_pool_size_--;
831#ifdef ESPHOME_DEBUG_SCHEDULER
832 ESP_LOGD(TAG,
"Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_size_);
836#ifdef ESPHOME_DEBUG_SCHEDULER
837 ESP_LOGD(TAG,
"Allocated new item (pool empty)");
839 auto *item =
new SchedulerItem();
840#ifdef ESPHOME_DEBUG_SCHEDULER
841 this->debug_live_items_++;
846#ifdef ESPHOME_DEBUG_SCHEDULER
847bool Scheduler::debug_verify_no_leak_()
const {
850 size_t accounted = this->items_.size() + this->to_add_.size() + this->scheduler_item_pool_size_;
851#ifndef ESPHOME_THREAD_SINGLE
852 accounted += this->defer_queue_.size();
854 if (accounted != this->debug_live_items_) {
856 "SCHEDULER LEAK DETECTED: live=%" PRIu32
" but accounted=%" PRIu32
" (items=%" PRIu32
" to_add=%" PRIu32
858#ifndef ESPHOME_THREAD_SINGLE
862 static_cast<uint32_t>(this->debug_live_items_),
static_cast<uint32_t>(accounted),
863 static_cast<uint32_t>(this->items_.size()),
static_cast<uint32_t>(this->to_add_.size()),
864 static_cast<uint32_t>(this->scheduler_item_pool_size_)
865#ifndef ESPHOME_THREAD_SINGLE
867 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
ESPHOME_LOG_TAG(TAG, CRASH_LOG_TAG)
void wake_scheduler_threadsafe()
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