This commit is contained in:
J. Nick Koston 2025-06-28 09:23:16 -05:00
parent 04b54353f1
commit 956959fc32
No known key found for this signature in database
4 changed files with 113 additions and 2 deletions

View File

@ -60,6 +60,10 @@ void Component::set_interval(const std::string &name, uint32_t interval, std::fu
App.scheduler.set_interval(this, name, interval, std::move(f));
}
void Component::set_interval(const char *name, uint32_t interval, std::function<void()> &&f) { // NOLINT
App.scheduler.set_interval(this, name, interval, std::move(f));
}
bool Component::cancel_interval(const std::string &name) { // NOLINT
return App.scheduler.cancel_interval(this, name);
}
@ -77,6 +81,10 @@ void Component::set_timeout(const std::string &name, uint32_t timeout, std::func
App.scheduler.set_timeout(this, name, timeout, std::move(f));
}
void Component::set_timeout(const char *name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
App.scheduler.set_timeout(this, name, timeout, std::move(f));
}
bool Component::cancel_timeout(const std::string &name) { // NOLINT
return App.scheduler.cancel_timeout(this, name);
}

View File

@ -260,6 +260,22 @@ class Component {
*/
void set_interval(const std::string &name, uint32_t interval, std::function<void()> &&f); // NOLINT
/** Set an interval function with a const char* name.
*
* IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
* This means the name should be:
* - A string literal (e.g., "update")
* - A static const char* variable
* - A pointer with lifetime >= the scheduled task
*
* For dynamic strings, use the std::string overload instead.
*
* @param name The identifier for this interval function (must have static lifetime)
* @param interval The interval in ms
* @param f The function to call
*/
void set_interval(const char *name, uint32_t interval, std::function<void()> &&f); // NOLINT
void set_interval(uint32_t interval, std::function<void()> &&f); // NOLINT
/** Cancel an interval function.
@ -328,6 +344,22 @@ class Component {
*/
void set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f); // NOLINT
/** Set a timeout function with a const char* name.
*
* IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
* This means the name should be:
* - A string literal (e.g., "init")
* - A static const char* variable
* - A pointer with lifetime >= the timeout duration
*
* For dynamic strings, use the std::string overload instead.
*
* @param name The identifier for this timeout function (must have static lifetime)
* @param timeout The timeout in ms
* @param f The function to call
*/
void set_timeout(const char *name, uint32_t timeout, std::function<void()> &&f); // NOLINT
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT
/** Cancel a timeout function.

View File

@ -17,6 +17,41 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
// Uncomment to debug scheduler
// #define ESPHOME_DEBUG_SCHEDULER
#ifdef ESPHOME_DEBUG_SCHEDULER
// Helper to validate that a pointer looks like it's in static memory
static void validate_static_string(const char *name) {
if (name == nullptr)
return;
// This is a heuristic check - stack and heap pointers are typically
// much higher in memory than static data
uintptr_t addr = reinterpret_cast<uintptr_t>(name);
// Create a stack variable to compare against
int stack_var;
uintptr_t stack_addr = reinterpret_cast<uintptr_t>(&stack_var);
// If the string pointer is near our stack variable, it's likely on the stack
// Using 8KB range as ESP32 main task stack is typically 8192 bytes
if (addr > (stack_addr - 0x2000) && addr < (stack_addr + 0x2000)) {
ESP_LOGW(TAG,
"WARNING: Scheduler name '%s' at %p appears to be on the stack - this is unsafe!\n"
" Stack reference at %p",
name, name, &stack_var);
}
// Also check if it might be on the heap by seeing if it's in a very different range
// This is platform-specific but generally heap is allocated far from static memory
static const char *static_str = "test";
uintptr_t static_addr = reinterpret_cast<uintptr_t>(static_str);
// If the address is very far from known static memory, it might be heap
if (addr > static_addr + 0x100000 || (static_addr > 0x100000 && addr < static_addr - 0x100000)) {
ESP_LOGW(TAG, "WARNING: Scheduler name '%s' at %p might be on heap (static ref at %p)", name, name, static_str);
}
}
#endif
// A note on locking: the `lock_` lock protects the `items_` and `to_add_` containers. It must be taken when writing to
// them (i.e. when adding/removing items, but not when changing items). As items are only deleted from the loop task,
// iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to
@ -50,6 +85,12 @@ void HOT Scheduler::set_timeout_impl_(Component *component, const NameType &name
item->set_name(name.c_str(), make_copy);
} else {
item->set_name(name, make_copy);
#ifdef ESPHOME_DEBUG_SCHEDULER
// Validate static strings in debug mode
if (!make_copy && name != nullptr) {
validate_static_string(name);
}
#endif
}
item->type = SchedulerItem::TIMEOUT;
@ -118,6 +159,12 @@ void HOT Scheduler::set_interval_impl_(Component *component, const NameType &nam
item->set_name(name.c_str(), make_copy);
} else {
item->set_name(name, make_copy);
#ifdef ESPHOME_DEBUG_SCHEDULER
// Validate static strings in debug mode
if (!make_copy && name != nullptr) {
validate_static_string(name);
}
#endif
}
item->type = SchedulerItem::INTERVAL;
@ -238,9 +285,10 @@ void HOT Scheduler::call() {
this->pop_raw_();
this->lock_.unlock();
const char *name = item->get_name();
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
item->get_type_str(), item->get_source(), item->get_name(), item->interval, item->next_execution_ - now,
item->next_execution_);
item->get_type_str(), item->get_source(), name ? name : "(null)", item->interval,
item->next_execution_ - now, item->next_execution_);
old_items.push_back(std::move(item));
}

View File

@ -14,10 +14,33 @@ class Scheduler {
public:
// Public API - accepts std::string for backward compatibility
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func);
/** Set a timeout with a const char* name.
*
* IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
* This means the name should be:
* - A string literal (e.g., "update")
* - A static const char* variable
* - A pointer with lifetime >= the scheduled task
*
* For dynamic strings, use the std::string overload instead.
*/
void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
bool cancel_timeout(Component *component, const std::string &name);
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
/** Set an interval with a const char* name.
*
* IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item.
* This means the name should be:
* - A string literal (e.g., "update")
* - A static const char* variable
* - A pointer with lifetime >= the scheduled task
*
* For dynamic strings, use the std::string overload instead.
*/
void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
bool cancel_interval(Component *component, const std::string &name);