diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index b7e99d4603..096291426f 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -524,30 +524,30 @@ class Scheduler { std::vector to_add_; #ifndef ESPHOME_THREAD_SINGLE - // Fast-path counter for process_to_add() to skip taking the lock when there is - // nothing to add. Uses std::atomic on platforms that support it, plain uint32_t - // otherwise. On non-atomic platforms, callers must hold the scheduler lock when - // mutating this counter. Not needed on single-threaded platforms where we can - // check to_add_.empty() directly. + // Fast-path counter for process_to_add() to skip taking the lock when there + // is nothing to add. std::atomic on ATOMICS; volatile uint32_t on NO_ATOMICS + // (aligned 32-bit reads are atomic on ARMv5TE — BK72xx — and volatile + // prevents the compiler caching/eliding the read). On NO_ATOMICS, callers + // must hold lock_ for any RMW mutation. Not needed on SINGLE. #ifdef ESPHOME_THREAD_MULTI_ATOMICS std::atomic to_add_count_{0}; #else - uint32_t to_add_count_{0}; + volatile uint32_t to_add_count_{0}; #endif #endif /* ESPHOME_THREAD_SINGLE */ - // Fast-path helper for process_to_add() to decide if it can try the lock-free path. - // - On ESPHOME_THREAD_SINGLE: direct container check is safe (no concurrent writers). - // - On ESPHOME_THREAD_MULTI_ATOMICS: performs a lock-free check via to_add_count_. - // - On ESPHOME_THREAD_MULTI_NO_ATOMICS: always returns false to force the caller - // down the locked path; this is NOT a lock-free emptiness check on that platform. + // Fast-path helper for process_to_add() to decide if it can skip the lock. + // - SINGLE: direct container check (no concurrent writers). + // - ATOMICS: lock-free load of to_add_count_. + // - NO_ATOMICS: volatile read. A stale 0 is benign — next call() iteration + // observes the update; RMW mutation is still under lock_. bool to_add_empty_() const { #ifdef ESPHOME_THREAD_SINGLE return this->to_add_.empty(); #elif defined(ESPHOME_THREAD_MULTI_ATOMICS) return this->to_add_count_.load(std::memory_order_relaxed) == 0; #else - return false; + return this->to_add_count_ == 0; #endif } @@ -580,20 +580,20 @@ class Scheduler { std::vector defer_queue_; // FIFO queue for defer() calls size_t defer_queue_front_{0}; // Index of first valid item in defer_queue_ (tracks consumed items) - // Fast-path counter for process_defer_queue_() to skip lock when nothing to process. + // Fast-path counter for process_defer_queue_() to skip lock when nothing to + // process. See to_add_count_ above for the volatile rationale on NO_ATOMICS. #ifdef ESPHOME_THREAD_MULTI_ATOMICS std::atomic defer_count_{0}; #else - uint32_t defer_count_{0}; + volatile uint32_t defer_count_{0}; #endif bool defer_empty_() const { // defer_queue_ only exists on multi-threaded platforms, so no ESPHOME_THREAD_SINGLE path - // ESPHOME_THREAD_MULTI_NO_ATOMICS: always take the lock #ifdef ESPHOME_THREAD_MULTI_ATOMICS return this->defer_count_.load(std::memory_order_relaxed) == 0; #else - return false; + return this->defer_count_ == 0; #endif } @@ -615,23 +615,23 @@ class Scheduler { #endif /* ESPHOME_THREAD_SINGLE */ - // Counter for items marked for removal. Incremented cross-thread in cancel_item_locked_(). - // On ESPHOME_THREAD_MULTI_ATOMICS this is read without a lock in the cleanup_() fast path; - // on ESPHOME_THREAD_MULTI_NO_ATOMICS the fast path is disabled so cleanup_() always takes the lock. + // Counter for items marked for removal. Incremented cross-thread in + // cancel_item_locked_(). See to_add_count_ above for the volatile rationale + // on NO_ATOMICS. #ifdef ESPHOME_THREAD_MULTI_ATOMICS std::atomic to_remove_{0}; +#elif defined(ESPHOME_THREAD_MULTI_NO_ATOMICS) + volatile uint32_t to_remove_{0}; #else - uint32_t to_remove_{0}; +uint32_t to_remove_{0}; #endif // Lock-free check if there are items to remove (for fast-path in cleanup_) bool to_remove_empty_() const { #ifdef ESPHOME_THREAD_MULTI_ATOMICS return this->to_remove_.load(std::memory_order_relaxed) == 0; -#elif defined(ESPHOME_THREAD_SINGLE) - return this->to_remove_ == 0; #else - return false; // Always take the lock path + return this->to_remove_ == 0; #endif }