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:
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 ")",
|
||||
|
||||
Reference in New Issue
Block a user