1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-30 14:43:51 +00:00

Merge branch 'reduce_main_loop' into integration

This commit is contained in:
J. Nick Koston
2025-07-04 10:46:38 -05:00
10 changed files with 411 additions and 81 deletions

View File

@@ -645,7 +645,7 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
}
// System APIs
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST)
#if defined(USE_ESP8266) || defined(USE_RP2040)
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
Mutex::~Mutex() {}
@@ -658,6 +658,13 @@ Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
#elif defined(USE_HOST)
// Host platform uses std::mutex for proper thread synchronization
Mutex::Mutex() { handle_ = new std::mutex(); }
Mutex::~Mutex() { delete static_cast<std::mutex *>(handle_); }
void Mutex::lock() { static_cast<std::mutex *>(handle_)->lock(); }
bool Mutex::try_lock() { return static_cast<std::mutex *>(handle_)->try_lock(); }
void Mutex::unlock() { static_cast<std::mutex *>(handle_)->unlock(); }
#endif
#if defined(USE_ESP8266)

View File

@@ -32,6 +32,10 @@
#include <semphr.h>
#endif
#ifdef USE_HOST
#include <mutex>
#endif
#define HOT __attribute__((hot))
#define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg)))
#define ESPHOME_ALWAYS_INLINE __attribute__((always_inline))

View File

@@ -225,15 +225,25 @@ void HOT Scheduler::call() {
// - Items execute in exact order they were deferred (FIFO guarantee)
// - No deferred items exist in to_add_, so processing order doesn't affect correctness
while (!this->defer_queue_.empty()) {
std::unique_ptr<SchedulerItem> item;
{
LockGuard guard{this->lock_};
if (this->defer_queue_.empty()) // Double-check with lock held
break;
item = std::move(this->defer_queue_.front());
this->defer_queue_.pop_front();
// IMPORTANT: The double-check pattern is REQUIRED for thread safety:
// 1. First check: !defer_queue_.empty() without lock (may become stale)
// 2. Acquire lock
// 3. Second check: defer_queue_.empty() with lock (authoritative)
// Between steps 1 and 2, another thread could have emptied the queue,
// so we must check again after acquiring the lock to avoid accessing an empty queue.
// Note: We use manual lock/unlock instead of RAII LockGuard to avoid creating
// unnecessary stack variables when the queue is empty after acquiring the lock.
this->lock_.lock();
if (this->defer_queue_.empty()) {
this->lock_.unlock();
break;
}
// Skip if item was marked for removal or component failed
auto item = std::move(this->defer_queue_.front());
this->defer_queue_.pop_front();
this->lock_.unlock();
// Execute callback without holding lock to prevent deadlocks
// if the callback tries to call defer() again
if (!this->should_skip_item_(item.get())) {
this->execute_item_(item.get());
}
@@ -312,8 +322,6 @@ void HOT Scheduler::call() {
this->pop_raw_();
continue;
}
App.set_current_component(item->component);
#ifdef ESPHOME_DEBUG_SCHEDULER
const char *item_name = item->get_name();
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",