ESPHome 2026.1.5
Loading...
Searching...
No Matches
scheduler.h
Go to the documentation of this file.
1#pragma once
2
4#include <cstring>
5#include <memory>
6#include <string>
7#include <vector>
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 for internal retry mechanism
25 friend void ::esphome::retry_handler(const std::shared_ptr<RetryArgs> &args);
26 // Allow DelayAction to call set_timer_common_ with skip_cancel=true for parallel script delays.
27 // This is needed to fix issue #10264 where parallel scripts with delays interfere with each other.
28 // We use friend instead of a public API because skip_cancel is dangerous - it can cause delays
29 // to accumulate and overload the scheduler if misused.
30 template<typename... Ts> friend class DelayAction;
31
32 public:
33 // std::string overload - deprecated, use const char* or uint32_t instead
34 // Remove before 2026.7.0
35 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
36 void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func);
37
46 void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
48 void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function<void()> func);
49
50 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
51 bool cancel_timeout(Component *component, const std::string &name);
52 bool cancel_timeout(Component *component, const char *name);
53 bool cancel_timeout(Component *component, uint32_t id);
54
55 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
56 void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
57
66 void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
68 void set_interval(Component *component, uint32_t id, uint32_t interval, std::function<void()> func);
69
70 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
71 bool cancel_interval(Component *component, const std::string &name);
72 bool cancel_interval(Component *component, const char *name);
73 bool cancel_interval(Component *component, uint32_t id);
74
75 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
76 void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
77 std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
78 void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
79 std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
81 void set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
82 std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
83
84 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
85 bool cancel_retry(Component *component, const std::string &name);
86 bool cancel_retry(Component *component, const char *name);
87 bool cancel_retry(Component *component, uint32_t id);
88
89 // Calculate when the next scheduled item should run
90 // @param now Fresh timestamp from millis() - must not be stale/cached
91 // Returns the time in milliseconds until the next scheduled item, or nullopt if no items
92 // This method performs cleanup of removed items before checking the schedule
93 // IMPORTANT: This method should only be called from the main thread (loop task).
94 optional<uint32_t> next_schedule_in(uint32_t now);
95
96 // Execute all scheduled items that are ready
97 // @param now Fresh timestamp from millis() - must not be stale/cached
98 void call(uint32_t now);
99
100 void process_to_add();
101
102 // Name storage type discriminator for SchedulerItem
103 // Used to distinguish between static strings, hashed strings, and numeric IDs
104 enum class NameType : uint8_t {
105 STATIC_STRING = 0, // const char* pointer to static/flash storage
106 HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
107 NUMERIC_ID = 2 // uint32_t numeric identifier
108 };
109
110 protected:
111 struct SchedulerItem {
112 // Ordered by size to minimize padding
113 Component *component;
114 // Optimized name storage using tagged union - zero heap allocation
115 union {
116 const char *static_name; // For STATIC_STRING (string literals, no allocation)
117 uint32_t hash_or_id; // For HASHED_STRING or NUMERIC_ID
118 } name_;
119 uint32_t interval;
120 // Split time to handle millis() rollover. The scheduler combines the 32-bit millis()
121 // with a 16-bit rollover counter to create a 48-bit time space (using 32+16 bits).
122 // This is intentionally limited to 48 bits, not stored as a full 64-bit value.
123 // With 49.7 days per 32-bit rollover, the 16-bit counter supports
124 // 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
125 // even when devices run for months. Split into two fields for better memory
126 // alignment on 32-bit systems.
127 uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value)
128 std::function<void()> callback;
129 uint16_t next_execution_high_; // Upper 16 bits (millis_major counter)
130
131#ifdef ESPHOME_THREAD_MULTI_ATOMICS
132 // Multi-threaded with atomics: use atomic for lock-free access
133 // Place atomic<bool> separately since it can't be packed with bit fields
134 std::atomic<bool> remove{false};
135
136 // Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
137 enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
138 NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
139 bool is_retry : 1; // True if this is a retry timeout
140 // 4 bits padding
141#else
142 // Single-threaded or multi-threaded without atomics: can pack all fields together
143 // Bit-packed fields (5 bits used, 3 bits padding in 1 byte)
144 enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
145 bool remove : 1;
146 NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
147 bool is_retry : 1; // True if this is a retry timeout
148 // 3 bits padding
149#endif
150
151 // Constructor
152 SchedulerItem()
153 : component(nullptr),
154 interval(0),
155 next_execution_low_(0),
156 next_execution_high_(0),
157#ifdef ESPHOME_THREAD_MULTI_ATOMICS
158 // remove is initialized in the member declaration as std::atomic<bool>{false}
159 type(TIMEOUT),
160 name_type_(NameType::STATIC_STRING),
161 is_retry(false) {
162#else
163 type(TIMEOUT),
164 remove(false),
165 name_type_(NameType::STATIC_STRING),
166 is_retry(false) {
167#endif
168 name_.static_name = nullptr;
169 }
170
171 // Destructor - no dynamic memory to clean up
172 ~SchedulerItem() = default;
173
174 // Delete copy operations to prevent accidental copies
175 SchedulerItem(const SchedulerItem &) = delete;
176 SchedulerItem &operator=(const SchedulerItem &) = delete;
177
178 // Delete move operations: SchedulerItem objects are only managed via unique_ptr, never moved directly
179 SchedulerItem(SchedulerItem &&) = delete;
180 SchedulerItem &operator=(SchedulerItem &&) = delete;
181
182 // Helper to get the static name (only valid for STATIC_STRING type)
183 const char *get_name() const { return (name_type_ == NameType::STATIC_STRING) ? name_.static_name : nullptr; }
184
185 // Helper to get the hash or numeric ID (only valid for HASHED_STRING or NUMERIC_ID types)
186 uint32_t get_name_hash_or_id() const { return (name_type_ != NameType::STATIC_STRING) ? name_.hash_or_id : 0; }
187
188 // Helper to get the name type
189 NameType get_name_type() const { return name_type_; }
190
191 // Helper to set a static string name (no allocation)
192 void set_static_name(const char *name) {
193 name_.static_name = name;
194 name_type_ = NameType::STATIC_STRING;
195 }
196
197 // Helper to set a hashed string name (hash computed from std::string)
198 void set_hashed_name(uint32_t hash) {
199 name_.hash_or_id = hash;
200 name_type_ = NameType::HASHED_STRING;
201 }
202
203 // Helper to set a numeric ID name
204 void set_numeric_id(uint32_t id) {
205 name_.hash_or_id = id;
206 name_type_ = NameType::NUMERIC_ID;
207 }
208
209 static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
210
211 // Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility.
212 // The upper 16 bits of the 64-bit value are always zero, which is fine since
213 // millis_major_ is also 16 bits and they must match.
214 constexpr uint64_t get_next_execution() const {
215 return (static_cast<uint64_t>(next_execution_high_) << 32) | next_execution_low_;
216 }
217
218 constexpr void set_next_execution(uint64_t value) {
219 next_execution_low_ = static_cast<uint32_t>(value);
220 // Cast to uint16_t intentionally truncates to lower 16 bits of the upper 32 bits.
221 // This is correct because millis_major_ that creates these values is also 16 bits.
222 next_execution_high_ = static_cast<uint16_t>(value >> 32);
223 }
224 constexpr const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; }
225 const LogString *get_source() const { return component ? component->get_component_log_str() : LOG_STR("unknown"); }
226 };
227
228 // Common implementation for both timeout and interval
229 // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
230 void set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type, const char *static_name,
231 uint32_t hash_or_id, uint32_t delay, std::function<void()> func, bool is_retry = false,
232 bool skip_cancel = false);
233
234 // Common implementation for retry
235 // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
236 void set_retry_common_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
237 uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
238 float backoff_increase_factor);
239 // Common implementation for cancel_retry
240 bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
241
242 uint64_t millis_64_(uint32_t now);
243 // Cleanup logically deleted items from the scheduler
244 // Returns the number of items remaining after cleanup
245 // IMPORTANT: This method should only be called from the main thread (loop task).
246 size_t cleanup_();
247 // Remove and return the front item from the heap
248 // IMPORTANT: Caller must hold the scheduler lock before calling this function.
249 std::unique_ptr<SchedulerItem> pop_raw_locked_();
250 // Get or create a scheduler item from the pool
251 // IMPORTANT: Caller must hold the scheduler lock before calling this function.
252 std::unique_ptr<SchedulerItem> get_item_from_pool_locked_();
253
254 private:
255 // Helper to cancel items - must be called with lock held
256 // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
257 bool cancel_item_locked_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
258 SchedulerItem::Type type, bool match_retry = false);
259
260 // Common implementation for cancel operations - handles locking
261 bool cancel_item_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
262 SchedulerItem::Type type, bool match_retry = false);
263
264 // Helper to check if two static string names match
265 inline bool HOT names_match_static_(const char *name1, const char *name2) const {
266 // Check pointer equality first (common for static strings), then string contents
267 // The core ESPHome codebase uses static strings (const char*) for component names,
268 // making pointer comparison effective. The std::string overloads exist only for
269 // compatibility with external components but are rarely used in practice.
270 return (name1 != nullptr && name2 != nullptr) && ((name1 == name2) || (strcmp(name1, name2) == 0));
271 }
272
273 // Helper function to check if item matches criteria for cancellation
274 // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
275 // IMPORTANT: Must be called with scheduler lock held
276 inline bool HOT matches_item_locked_(const std::unique_ptr<SchedulerItem> &item, Component *component,
277 NameType name_type, const char *static_name, uint32_t hash_or_id,
278 SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
279 // THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded
280 // platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries.
281 // PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and
282 // has_cancelled_timeout_in_container_locked_()), but this check provides defense-in-depth: helper
283 // functions should be safe regardless of caller behavior.
284 // Fixes: https://github.com/esphome/esphome/issues/11940
285 if (!item)
286 return false;
287 if (item->component != component || item->type != type || (skip_removed && item->remove) ||
288 (match_retry && !item->is_retry)) {
289 return false;
290 }
291 // Name type must match
292 if (item->get_name_type() != name_type)
293 return false;
294 // For static strings, compare the string content; for hash/ID, compare the value
295 if (name_type == NameType::STATIC_STRING) {
296 return this->names_match_static_(item->get_name(), static_name);
297 }
298 return item->get_name_hash_or_id() == hash_or_id;
299 }
300
301 // Helper to execute a scheduler item
302 uint32_t execute_item_(SchedulerItem *item, uint32_t now);
303
304 // Helper to check if item should be skipped
305 bool should_skip_item_(SchedulerItem *item) const {
306 return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed());
307 }
308
309 // Helper to recycle a SchedulerItem back to the pool.
310 // IMPORTANT: Only call from main loop context! Recycling clears the callback,
311 // so calling from another thread while the callback is executing causes use-after-free.
312 // IMPORTANT: Caller must hold the scheduler lock before calling this function.
313 void recycle_item_main_loop_(std::unique_ptr<SchedulerItem> item);
314
315 // Helper to perform full cleanup when too many items are cancelled
316 void full_cleanup_removed_items_();
317
318#ifdef ESPHOME_DEBUG_SCHEDULER
319 // Helper for debug logging in set_timer_common_ - extracted to reduce code size
320 void debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, uint32_t hash_or_id,
321 SchedulerItem::Type type, uint32_t delay, uint64_t now);
322#endif /* ESPHOME_DEBUG_SCHEDULER */
323
324#ifndef ESPHOME_THREAD_SINGLE
325 // Helper to process defer queue - inline for performance in hot path
326 inline void process_defer_queue_(uint32_t &now) {
327 // Process defer queue first to guarantee FIFO execution order for deferred items.
328 // Previously, defer() used the heap which gave undefined order for equal timestamps,
329 // causing race conditions on multi-core systems (ESP32, BK7200).
330 // With the defer queue:
331 // - Deferred items (delay=0) go directly to defer_queue_ in set_timer_common_
332 // - Items execute in exact order they were deferred (FIFO guarantee)
333 // - No deferred items exist in to_add_, so processing order doesn't affect correctness
334 // Single-core platforms don't use this queue and fall back to the heap-based approach.
335 //
336 // Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still
337 // processed here. They are skipped during execution by should_skip_item_().
338 // This is intentional - no memory leak occurs.
339 //
340 // We use an index (defer_queue_front_) to track the read position instead of calling
341 // erase() on every pop, which would be O(n). The queue is processed once per loop -
342 // any items added during processing are left for the next loop iteration.
343
344 // Snapshot the queue end point - only process items that existed at loop start
345 // Items added during processing (by callbacks or other threads) run next loop
346 // No lock needed: single consumer (main loop), stale read just means we process less this iteration
347 size_t defer_queue_end = this->defer_queue_.size();
348
349 while (this->defer_queue_front_ < defer_queue_end) {
350 std::unique_ptr<SchedulerItem> item;
351 {
352 LockGuard lock(this->lock_);
353 // SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_.
354 // This is intentional and safe because:
355 // 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function
356 // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_
357 // and has_cancelled_timeout_in_container_locked_ in scheduler.h)
358 // 3. The lock protects concurrent access, but the nullptr remains until cleanup
359 item = std::move(this->defer_queue_[this->defer_queue_front_]);
360 this->defer_queue_front_++;
361 }
362
363 // Execute callback without holding lock to prevent deadlocks
364 // if the callback tries to call defer() again
365 if (!this->should_skip_item_(item.get())) {
366 now = this->execute_item_(item.get(), now);
367 }
368 // Recycle the defer item after execution
369 {
370 LockGuard lock(this->lock_);
371 this->recycle_item_main_loop_(std::move(item));
372 }
373 }
374
375 // If we've consumed all items up to the snapshot point, clean up the dead space
376 // Single consumer (main loop), so no lock needed for this check
377 if (this->defer_queue_front_ >= defer_queue_end) {
378 LockGuard lock(this->lock_);
379 this->cleanup_defer_queue_locked_();
380 }
381 }
382
383 // Helper to cleanup defer_queue_ after processing
384 // IMPORTANT: Caller must hold the scheduler lock before calling this function.
385 inline void cleanup_defer_queue_locked_() {
386 // Check if new items were added by producers during processing
387 if (this->defer_queue_front_ >= this->defer_queue_.size()) {
388 // Common case: no new items - clear everything
389 this->defer_queue_.clear();
390 } else {
391 // Rare case: new items were added during processing - compact the vector
392 // This only happens when:
393 // 1. A deferred callback calls defer() again, or
394 // 2. Another thread calls defer() while we're processing
395 //
396 // Move unprocessed items (added during this loop) to the front for next iteration
397 //
398 // SAFETY: Compacted items may include cancelled items (marked for removal via
399 // cancel_item_locked_() during execution). This is safe because should_skip_item_()
400 // checks is_item_removed_() before executing, so cancelled items will be skipped
401 // and recycled on the next loop iteration.
402 size_t remaining = this->defer_queue_.size() - this->defer_queue_front_;
403 for (size_t i = 0; i < remaining; i++) {
404 this->defer_queue_[i] = std::move(this->defer_queue_[this->defer_queue_front_ + i]);
405 }
406 this->defer_queue_.resize(remaining);
407 }
408 this->defer_queue_front_ = 0;
409 }
410#endif /* not ESPHOME_THREAD_SINGLE */
411
412 // Helper to check if item is marked for removal (platform-specific)
413 // Returns true if item should be skipped, handles platform-specific synchronization
414 // For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this
415 // function.
416 bool is_item_removed_(SchedulerItem *item) const {
417#ifdef ESPHOME_THREAD_MULTI_ATOMICS
418 // Multi-threaded with atomics: use atomic load for lock-free access
419 return item->remove.load(std::memory_order_acquire);
420#else
421 // Single-threaded (ESPHOME_THREAD_SINGLE) or
422 // multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct read
423 // For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock!
424 return item->remove;
425#endif
426 }
427
428 // Helper to set item removal flag (platform-specific)
429 // For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this
430 // function. Uses memory_order_release when setting to true (for cancellation synchronization),
431 // and memory_order_relaxed when setting to false (for initialization).
432 void set_item_removed_(SchedulerItem *item, bool removed) {
433#ifdef ESPHOME_THREAD_MULTI_ATOMICS
434 // Multi-threaded with atomics: use atomic store with appropriate ordering
435 // Release ordering when setting to true ensures cancellation is visible to other threads
436 // Relaxed ordering when setting to false is sufficient for initialization
437 item->remove.store(removed, removed ? std::memory_order_release : std::memory_order_relaxed);
438#else
439 // Single-threaded (ESPHOME_THREAD_SINGLE) or
440 // multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct write
441 // For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock!
442 item->remove = removed;
443#endif
444 }
445
446 // Helper to mark matching items in a container as removed
447 // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
448 // Returns the number of items marked for removal
449 // IMPORTANT: Must be called with scheduler lock held
450 template<typename Container>
451 size_t mark_matching_items_removed_locked_(Container &container, Component *component, NameType name_type,
452 const char *static_name, uint32_t hash_or_id, SchedulerItem::Type type,
453 bool match_retry) {
454 size_t count = 0;
455 for (auto &item : container) {
456 // Skip nullptr items (can happen in defer_queue_ when items are being processed)
457 // The defer_queue_ uses index-based processing: items are std::moved out but left in the
458 // vector as nullptr until cleanup. Even though this function is called with lock held,
459 // the vector can still contain nullptr items from the processing loop. This check prevents crashes.
460 if (!item)
461 continue;
462 if (this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) {
463 this->set_item_removed_(item.get(), true);
464 count++;
465 }
466 }
467 return count;
468 }
469
470 // Template helper to check if any item in a container matches our criteria
471 // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
472 // IMPORTANT: Must be called with scheduler lock held
473 template<typename Container>
474 bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, NameType name_type,
475 const char *static_name, uint32_t hash_or_id,
476 bool match_retry) const {
477 for (const auto &item : container) {
478 // Skip nullptr items (can happen in defer_queue_ when items are being processed)
479 // The defer_queue_ uses index-based processing: items are std::moved out but left in the
480 // vector as nullptr until cleanup. If this function is called during defer queue processing,
481 // it will iterate over these nullptr items. This check prevents crashes.
482 if (!item)
483 continue;
484 if (is_item_removed_(item.get()) &&
485 this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, SchedulerItem::TIMEOUT,
486 match_retry, /* skip_removed= */ false)) {
487 return true;
488 }
489 }
490 return false;
491 }
492
493 Mutex lock_;
494 std::vector<std::unique_ptr<SchedulerItem>> items_;
495 std::vector<std::unique_ptr<SchedulerItem>> to_add_;
496#ifndef ESPHOME_THREAD_SINGLE
497 // Single-core platforms don't need the defer queue and save ~32 bytes of RAM
498 // Using std::vector instead of std::deque avoids 512-byte chunked allocations
499 // Index tracking avoids O(n) erase() calls when draining the queue each loop
500 std::vector<std::unique_ptr<SchedulerItem>> defer_queue_; // FIFO queue for defer() calls
501 size_t defer_queue_front_{0}; // Index of first valid item in defer_queue_ (tracks consumed items)
502#endif /* ESPHOME_THREAD_SINGLE */
503 uint32_t to_remove_{0};
504
505 // Memory pool for recycling SchedulerItem objects to reduce heap churn.
506 // Design decisions:
507 // - std::vector is used instead of a fixed array because many systems only need 1-2 scheduler items
508 // - The vector grows dynamically up to MAX_POOL_SIZE (5) only when needed, saving memory on simple setups
509 // - Pool size of 5 matches typical usage (2-4 timers) while keeping memory overhead low (~250 bytes on ESP32)
510 // - The pool significantly reduces heap fragmentation which is critical because heap allocation/deallocation
511 // can stall the entire system, causing timing issues and dropped events for any components that need
512 // to synchronize between tasks (see https://github.com/esphome/backlog/issues/52)
513 std::vector<std::unique_ptr<SchedulerItem>> scheduler_item_pool_;
514
515#ifdef ESPHOME_THREAD_MULTI_ATOMICS
516 /*
517 * Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
518 *
519 * MEMORY-ORDERING NOTE
520 * --------------------
521 * `last_millis_` and `millis_major_` form a single 64-bit timestamp split in half.
522 * Writers publish `last_millis_` with memory_order_release and readers use
523 * memory_order_acquire. This ensures that once a reader sees the new low word,
524 * it also observes the corresponding increment of `millis_major_`.
525 */
526 std::atomic<uint32_t> last_millis_{0};
527#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
528 // Platforms without atomic support or single-threaded platforms
529 uint32_t last_millis_{0};
530#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
531
532 /*
533 * Upper 16 bits of the 64-bit millis counter. Incremented only while holding
534 * `lock_`; read concurrently. Atomic (relaxed) avoids a formal data race.
535 * Ordering relative to `last_millis_` is provided by its release store and the
536 * corresponding acquire loads.
537 */
538#ifdef ESPHOME_THREAD_MULTI_ATOMICS
539 std::atomic<uint16_t> millis_major_{0};
540#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
541 uint16_t millis_major_{0};
542#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
543};
544
545} // namespace esphome
const Component * component
Definition component.cpp:37
uint16_t type
uint16_t id
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void retry_handler(const std::shared_ptr< RetryArgs > &args)
struct ESPDEPRECATED("Use std::index_sequence instead. Removed in 2026.6.0", "2025.12.0") seq
Definition automation.h:24