ESPHome 2025.8.0b1
Loading...
Searching...
No Matches
scheduler.h
Go to the documentation of this file.
1#pragma once
2
4#include <vector>
5#include <memory>
6#include <cstring>
7#include <deque>
8#ifdef ESPHOME_THREAD_MULTI_ATOMICS
9#include <atomic>
10#endif
11
14
15namespace esphome {
16
17class Component;
18struct RetryArgs;
19
20// Forward declaration of retry_handler - needs to be non-static for friend declaration
21void retry_handler(const std::shared_ptr<RetryArgs> &args);
22
23class Scheduler {
24 // Allow retry_handler to access protected members
25 friend void ::esphome::retry_handler(const std::shared_ptr<RetryArgs> &args);
26
27 public:
28 // Public API - accepts std::string for backward compatibility
29 void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func);
30
41 void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
42
43 bool cancel_timeout(Component *component, const std::string &name);
44 bool cancel_timeout(Component *component, const char *name);
45
46 void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
47
58 void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
59
60 bool cancel_interval(Component *component, const std::string &name);
61 bool cancel_interval(Component *component, const char *name);
62 void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
63 std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
64 void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
65 std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
66 bool cancel_retry(Component *component, const std::string &name);
67 bool cancel_retry(Component *component, const char *name);
68
69 // Calculate when the next scheduled item should run
70 // @param now Fresh timestamp from millis() - must not be stale/cached
71 // Returns the time in milliseconds until the next scheduled item, or nullopt if no items
72 // This method performs cleanup of removed items before checking the schedule
73 // IMPORTANT: This method should only be called from the main thread (loop task).
75
76 // Execute all scheduled items that are ready
77 // @param now Fresh timestamp from millis() - must not be stale/cached
78 void call(uint32_t now);
79
80 void process_to_add();
81
82 protected:
84 // Ordered by size to minimize padding
86 uint32_t interval;
87 // 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis()
88 // with a 16-bit rollover counter to create a 64-bit time that won't roll over for
89 // billions of years. This ensures correct scheduling even when devices run for months.
91
92 // Optimized name storage using tagged union
93 union {
94 const char *static_name; // For string literals (no allocation)
95 char *dynamic_name; // For allocated strings
97
98 std::function<void()> callback;
99
100 // Bit-packed fields to minimize padding
101 enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
102 bool remove : 1;
103 bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
104 bool is_retry : 1; // True if this is a retry timeout
105 // 4 bits padding
106
107 // Constructor
109 : component(nullptr),
110 interval(0),
112 type(TIMEOUT),
113 remove(false),
114 name_is_dynamic(false),
115 is_retry(false) {
116 name_.static_name = nullptr;
117 }
118
119 // Destructor to clean up dynamic names
121 if (name_is_dynamic) {
122 delete[] name_.dynamic_name;
123 }
124 }
125
126 // Delete copy operations to prevent accidental copies
127 SchedulerItem(const SchedulerItem &) = delete;
129
130 // Delete move operations: SchedulerItem objects are only managed via unique_ptr, never moved directly
133
134 // Helper to get the name regardless of storage type
135 const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; }
136
137 // Helper to set name with proper ownership
138 void set_name(const char *name, bool make_copy = false) {
139 // Clean up old dynamic name if any
140 if (name_is_dynamic && name_.dynamic_name) {
141 delete[] name_.dynamic_name;
142 name_is_dynamic = false;
143 }
144
145 if (!name) {
146 // nullptr case - no name provided
147 name_.static_name = nullptr;
148 } else if (make_copy) {
149 // Make a copy for dynamic strings (including empty strings)
150 size_t len = strlen(name);
151 name_.dynamic_name = new char[len + 1];
152 memcpy(name_.dynamic_name, name, len + 1);
153 name_is_dynamic = true;
154 } else {
155 // Use static string directly (including empty strings)
156 name_.static_name = name;
157 }
158 }
159
160 static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
161 const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; }
162 const char *get_source() const { return component ? component->get_component_source() : "unknown"; }
163 };
164
165 // Common implementation for both timeout and interval
166 void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
167 uint32_t delay, std::function<void()> func, bool is_retry = false);
168
169 // Common implementation for retry
170 void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time,
171 uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, float backoff_increase_factor);
172
173 uint64_t millis_64_(uint32_t now);
174 // Cleanup logically deleted items from the scheduler
175 // Returns the number of items remaining after cleanup
176 // IMPORTANT: This method should only be called from the main thread (loop task).
177 size_t cleanup_();
178 void pop_raw_();
179
180 private:
181 // Helper to cancel items by name - must be called with lock held
182 bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false);
183
184 // Helper to extract name as const char* from either static string or std::string
185 inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) {
186 return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
187 }
188
189 // Common implementation for cancel operations
190 bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
191
192 // Helper function to check if item matches criteria for cancellation
193 inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
194 SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
195 if (item->component != component || item->type != type || (skip_removed && item->remove) ||
196 (match_retry && !item->is_retry)) {
197 return false;
198 }
199 const char *item_name = item->get_name();
200 if (item_name == nullptr) {
201 return false;
202 }
203 // Fast path: if pointers are equal
204 // This is effective because the core ESPHome codebase uses static strings (const char*)
205 // for component names. The std::string overloads exist only for compatibility with
206 // external components, but are rarely used in practice.
207 if (item_name == name_cstr) {
208 return true;
209 }
210 // Slow path: compare string contents
211 return strcmp(name_cstr, item_name) == 0;
212 }
213
214 // Helper to execute a scheduler item
215 void execute_item_(SchedulerItem *item, uint32_t now);
216
217 // Helper to check if item should be skipped
218 bool should_skip_item_(const SchedulerItem *item) const {
219 return item->remove || (item->component != nullptr && item->component->is_failed());
220 }
221
222 // Template helper to check if any item in a container matches our criteria
223 template<typename Container>
224 bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
225 bool match_retry) const {
226 for (const auto &item : container) {
227 if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
228 /* skip_removed= */ false)) {
229 return true;
230 }
231 }
232 return false;
233 }
234
235 Mutex lock_;
236 std::vector<std::unique_ptr<SchedulerItem>> items_;
237 std::vector<std::unique_ptr<SchedulerItem>> to_add_;
238#ifndef ESPHOME_THREAD_SINGLE
239 // Single-core platforms don't need the defer queue and save 40 bytes of RAM
240 std::deque<std::unique_ptr<SchedulerItem>> defer_queue_; // FIFO queue for defer() calls
241#endif /* ESPHOME_THREAD_SINGLE */
242 uint32_t to_remove_{0};
243
244#ifdef ESPHOME_THREAD_MULTI_ATOMICS
245 /*
246 * Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
247 *
248 * MEMORY-ORDERING NOTE
249 * --------------------
250 * `last_millis_` and `millis_major_` form a single 64-bit timestamp split in half.
251 * Writers publish `last_millis_` with memory_order_release and readers use
252 * memory_order_acquire. This ensures that once a reader sees the new low word,
253 * it also observes the corresponding increment of `millis_major_`.
254 */
255 std::atomic<uint32_t> last_millis_{0};
256#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
257 // Platforms without atomic support or single-threaded platforms
258 uint32_t last_millis_{0};
259#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
260
261 /*
262 * Upper 16 bits of the 64-bit millis counter. Incremented only while holding
263 * `lock_`; read concurrently. Atomic (relaxed) avoids a formal data race.
264 * Ordering relative to `last_millis_` is provided by its release store and the
265 * corresponding acquire loads.
266 */
267#ifdef ESPHOME_THREAD_MULTI_ATOMICS
268 std::atomic<uint16_t> millis_major_{0};
269#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
270 uint16_t millis_major_{0};
271#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
272};
273
274} // namespace esphome
const char * get_component_source() const
Get the integration where this component was declared as a string.
void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time, uint8_t max_attempts, std::function< RetryResult(uint8_t)> func, float backoff_increase_factor)
bool cancel_retry(Component *component, const std::string &name)
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, uint32_t delay, std::function< void()> func, bool is_retry=false)
Definition scheduler.cpp:67
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function< RetryResult(uint8_t)> func, float backoff_increase_factor=1.0f)
void call(uint32_t now)
bool cancel_timeout(Component *component, const std::string &name)
bool cancel_interval(Component *component, const std::string &name)
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function< void()> func)
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function< void()> func)
uint64_t millis_64_(uint32_t now)
optional< uint32_t > next_schedule_in(uint32_t now)
uint8_t type
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void retry_handler(const std::shared_ptr< RetryArgs > &args)
std::string size_t len
Definition helpers.h:279
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
const char * get_type_str() const
Definition scheduler.h:161
enum esphome::Scheduler::SchedulerItem::Type type
SchedulerItem & operator=(const SchedulerItem &)=delete
void set_name(const char *name, bool make_copy=false)
Definition scheduler.h:138
SchedulerItem(const SchedulerItem &)=delete
std::function< void()> callback
Definition scheduler.h:98
union esphome::Scheduler::SchedulerItem::@174 name_
SchedulerItem & operator=(SchedulerItem &&)=delete
const char * get_source() const
Definition scheduler.h:162
const char * get_name() const
Definition scheduler.h:135
SchedulerItem(SchedulerItem &&)=delete
static bool cmp(const std::unique_ptr< SchedulerItem > &a, const std::unique_ptr< SchedulerItem > &b)