mirror of
https://github.com/esphome/esphome.git
synced 2026-09-27 15:00:24 +00:00
[core] Fix uint32_t underflow in WarnIfComponentBlockingGuard::finish()
When curr_time (from millis()) is less than started_ (the `now` passed to scheduler.call()), the subtraction wraps to a huge value (~4 billion). This triggers spurious blocking warnings with nonsensical times. This can happen when the scheduler's execute_item_() returns a millis() value that subsequent items use as their guard start, but the next scheduler.call() passes a `now` value from a slightly different source. Fix by skipping the blocking check when curr_time < started_ (underflow). Also restore the scheduler firing benchmark to use intervals with monotonically increasing fake time, now that the guard handles underflow.
This commit is contained in:
@@ -594,12 +594,17 @@ class WarnIfComponentBlockingGuard {
|
||||
// Inlined: the fast path is just millis() + subtract + compare
|
||||
inline uint32_t HOT finish() {
|
||||
uint32_t curr_time = millis();
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
this->record_runtime_stats_();
|
||||
#endif
|
||||
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] {
|
||||
warn_blocking(this->component_, blocking_time);
|
||||
// Guard against underflow: if curr_time < started_, the subtraction wraps
|
||||
// to a huge value. This can happen when the scheduler passes a `now` value
|
||||
// slightly ahead of real millis() (e.g. from execute_item_ return values).
|
||||
if (curr_time >= this->started_) [[likely]] {
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] {
|
||||
warn_blocking(this->component_, blocking_time);
|
||||
}
|
||||
}
|
||||
return curr_time;
|
||||
}
|
||||
|
||||
@@ -67,21 +67,16 @@ static void Scheduler_Call_5IntervalsFiring(benchmark::State &state) {
|
||||
int fire_count = 0;
|
||||
|
||||
// Add 5 intervals with 1ms period — they fire every call when time advances.
|
||||
// No inner loop needed: 5 heap pops + 5 callbacks + 5 heap pushes per call
|
||||
// is well above CodSpeed's ~60ns instrumentation overhead.
|
||||
// We use monotonically increasing fake time (now++) so intervals reliably fire.
|
||||
// The underflow guard in WarnIfComponentBlockingGuard::finish() (curr_time >= started_)
|
||||
// prevents warn_blocking from firing when fake time exceeds real millis().
|
||||
// Note: interval=0 causes infinite loop (reschedules at same now, never breaks).
|
||||
for (int i = 0; i < 5; i++) {
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i), 1, [&fire_count]() { fire_count++; });
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
|
||||
// Monotonically increasing fake time so intervals are due every call.
|
||||
// Can't use real millis() — it doesn't advance fast enough between calls.
|
||||
// Warm-up call outside the benchmark to trigger the blocking guard once
|
||||
// and ramp the component's warn_if_blocking_over_ threshold to max.
|
||||
uint32_t now = millis() + 100;
|
||||
scheduler.call(now);
|
||||
now++;
|
||||
|
||||
for (auto _ : state) {
|
||||
scheduler.call(now);
|
||||
|
||||
Reference in New Issue
Block a user