From 191e9dedc553271739e97e016117d32188e4663f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Sep 2025 16:30:37 -0500 Subject: [PATCH 1/4] Update esphome/core/scheduler.h --- esphome/core/scheduler.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 4a35dd88b0..c4bbbf1035 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -185,7 +185,6 @@ class Scheduler { static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); - // Helper methods to work with split execution time (constexpr for optimization) constexpr uint64_t get_next_execution() const { return (static_cast(next_execution_high_) << 32) | next_execution_low_; } From ca0029e0024edc913486933b53c97676fa47abe7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Sep 2025 16:34:47 -0500 Subject: [PATCH 2/4] explain why its safe --- esphome/core/scheduler.h | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 4a35dd88b0..e44d7501cc 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -94,13 +94,15 @@ class Scheduler { char *dynamic_name; // For allocated strings } name_; uint32_t interval; - // Split 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis() - // with a 16-bit rollover counter to create a 64-bit time that won't roll over for - // billions of years. This ensures correct scheduling even when devices run for months. - // Split into two fields for better memory alignment on 32-bit systems. - uint32_t next_execution_low_; // Lower 32 bits of next execution time + // Split time to handle millis() rollover. The scheduler combines the 32-bit millis() + // with a 16-bit rollover counter to create a 48-bit time space (stored as 64-bit + // for compatibility). With 49.7 days per 32-bit rollover, the 16-bit counter + // supports 49.7 days × 65536 = ~8900 years. This ensures correct scheduling + // even when devices run for months. Split into two fields for better memory + // alignment on 32-bit systems. + uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value) std::function callback; - uint16_t next_execution_high_; // Upper 16 bits of next execution time + uint16_t next_execution_high_; // Upper 16 bits (millis_major counter) #ifdef ESPHOME_THREAD_MULTI_ATOMICS // Multi-threaded with atomics: use atomic for lock-free access @@ -186,12 +188,17 @@ class Scheduler { static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); // Helper methods to work with split execution time (constexpr for optimization) + // Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility. + // The upper 16 bits of the 64-bit value are always zero, which is fine since + // millis_major_ is also 16 bits and they must match. constexpr uint64_t get_next_execution() const { return (static_cast(next_execution_high_) << 32) | next_execution_low_; } constexpr void set_next_execution(uint64_t value) { next_execution_low_ = static_cast(value); + // Cast to uint16_t intentionally truncates to lower 16 bits of the upper 32 bits. + // This is correct because millis_major_ that creates these values is also 16 bits. next_execution_high_ = static_cast(value >> 32); } constexpr const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; } From 26e0151fee24fee795a25b2f84c47b701beeea41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Sep 2025 16:36:49 -0500 Subject: [PATCH 3/4] Update esphome/core/scheduler.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/core/scheduler.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 7ca0bc1064..6ae6d9af57 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -95,9 +95,10 @@ class Scheduler { } name_; uint32_t interval; // Split time to handle millis() rollover. The scheduler combines the 32-bit millis() - // with a 16-bit rollover counter to create a 48-bit time space (stored as 64-bit - // for compatibility). With 49.7 days per 32-bit rollover, the 16-bit counter - // supports 49.7 days × 65536 = ~8900 years. This ensures correct scheduling + // with a 16-bit rollover counter to create a 48-bit time space (using 32+16 bits). + // This is intentionally limited to 48 bits, not stored as a full 64-bit value. + // With 49.7 days per 32-bit rollover, the 16-bit counter supports + // 49.7 days × 65536 = ~8900 years. This ensures correct scheduling // even when devices run for months. Split into two fields for better memory // alignment on 32-bit systems. uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value) From 1298268937ab15b98d4579b09b4ba8fa1b30ce4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Sep 2025 16:37:01 -0500 Subject: [PATCH 4/4] Update esphome/core/scheduler.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/core/scheduler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 6ae6d9af57..a4d20b9948 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -161,7 +161,7 @@ class Scheduler { SchedulerItem &operator=(SchedulerItem &&) = delete; // Helper to get the name regardless of storage type - constexpr const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } + const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } // Helper to set name with proper ownership void set_name(const char *name, bool make_copy = false) {