mirror of
https://github.com/esphome/esphome.git
synced 2026-02-06 01:22:47 +08:00
[logger] Use RAII guards for recursion protection and optimize hot path (#13194)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -23,30 +23,57 @@ static const char *const TAG = "logger";
|
||||
// - Messages are serialized through main loop for proper console output
|
||||
// - Fallback to emergency console logging only if ring buffer is full
|
||||
// - WITHOUT task log buffer: Only emergency console output, no callbacks
|
||||
//
|
||||
// Optimized for the common case: 99.9% of logs come from the main thread
|
||||
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag))
|
||||
return;
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// Get task handle once - used for both main task check and passing to non-main thread handler
|
||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
||||
bool is_main_task = (current_task == main_task_);
|
||||
const bool is_main_task = (current_task == this->main_task_);
|
||||
#else // USE_HOST
|
||||
pthread_t current_thread = pthread_self();
|
||||
bool is_main_task = pthread_equal(current_thread, main_thread_);
|
||||
const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_);
|
||||
#endif
|
||||
|
||||
// Check and set recursion guard - uses pthread TLS for per-thread/task state
|
||||
if (this->check_and_set_task_log_recursion_(is_main_task)) {
|
||||
return; // Recursion detected
|
||||
}
|
||||
|
||||
// Main thread/task uses the shared buffer for efficiency
|
||||
if (is_main_task) {
|
||||
// Fast path: main thread, no recursion (99.9% of all logs)
|
||||
if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
|
||||
RecursionGuard guard(this->main_task_recursion_guard_);
|
||||
// Format and send to both console and callbacks
|
||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||
this->reset_task_log_recursion_(is_main_task);
|
||||
return;
|
||||
}
|
||||
|
||||
// Main task with recursion - silently drop to prevent infinite loop
|
||||
if (is_main_task) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-main thread handling (~0.1% of logs)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task);
|
||||
#else // USE_HOST
|
||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Handles non-main thread logging only
|
||||
// Kept separate from hot path to improve instruction cache performance
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||
TaskHandle_t current_task) {
|
||||
#else // USE_HOST
|
||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) {
|
||||
#endif
|
||||
// Check if already in recursion for this non-main thread/task
|
||||
if (this->is_non_main_task_recursive_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// RAII guard - automatically resets on any return path
|
||||
auto guard = this->make_non_main_task_guard_();
|
||||
|
||||
bool message_sent = false;
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
// For non-main threads/tasks, queue the message for callbacks
|
||||
@@ -85,21 +112,17 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
this->write_msg_(console_buffer, buffer_at);
|
||||
}
|
||||
|
||||
// Reset the recursion guard for this thread/task
|
||||
this->reset_task_log_recursion_(is_main_task);
|
||||
// RAII guard automatically resets on return
|
||||
}
|
||||
#else
|
||||
// Implementation for all other platforms
|
||||
// Implementation for all other platforms (single-task, no threading)
|
||||
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
|
||||
global_recursion_guard_ = true;
|
||||
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
// Format and send to both console and callbacks
|
||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY
|
||||
|
||||
@@ -130,7 +153,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
|
||||
global_recursion_guard_ = true;
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
this->tx_buffer_at_ = 0;
|
||||
|
||||
// Copy format string from progmem
|
||||
@@ -140,9 +163,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
|
||||
}
|
||||
|
||||
// Buffer full from copying format
|
||||
// Buffer full from copying format - RAII guard handles cleanup on return
|
||||
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
||||
global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,8 +183,6 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
|
||||
|
||||
@@ -229,6 +229,31 @@ class Logger : public Component {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// RAII guard for recursion flags - sets flag on construction, clears on destruction
|
||||
class RecursionGuard {
|
||||
public:
|
||||
explicit RecursionGuard(bool &flag) : flag_(flag) { flag_ = true; }
|
||||
~RecursionGuard() { flag_ = false; }
|
||||
RecursionGuard(const RecursionGuard &) = delete;
|
||||
RecursionGuard &operator=(const RecursionGuard &) = delete;
|
||||
RecursionGuard(RecursionGuard &&) = delete;
|
||||
RecursionGuard &operator=(RecursionGuard &&) = delete;
|
||||
|
||||
private:
|
||||
bool &flag_;
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY)
|
||||
// Handles non-main thread logging only (~0.1% of calls)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// ESP32/LibreTiny: Pass task handle to avoid calling xTaskGetCurrentTaskHandle() twice
|
||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||
TaskHandle_t current_task);
|
||||
#else // USE_HOST
|
||||
// Host: No task handle parameter needed (not used in send_message_thread_safe)
|
||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args);
|
||||
#endif
|
||||
#endif
|
||||
void process_messages_();
|
||||
void write_msg_(const char *msg, size_t len);
|
||||
|
||||
@@ -348,10 +373,10 @@ class Logger : public Component {
|
||||
const device *uart_dev_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
void *main_task_ = nullptr; // Only used for thread name identification
|
||||
void *main_task_{nullptr}; // Main thread/task for fast path comparison
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
pthread_t main_thread_{}; // Main thread for identification
|
||||
pthread_t main_thread_{}; // Main thread for pthread_equal() comparison
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// Task-specific recursion guards:
|
||||
@@ -434,29 +459,28 @@ class Logger : public Component {
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
const bool was_recursive = main_task_recursion_guard_;
|
||||
main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
// RAII guard for non-main task recursion using pthread TLS
|
||||
class NonMainTaskRecursionGuard {
|
||||
public:
|
||||
explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) {
|
||||
pthread_setspecific(key_, reinterpret_cast<void *>(1));
|
||||
}
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); }
|
||||
NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(NonMainTaskRecursionGuard &&) = delete;
|
||||
|
||||
intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
|
||||
if (current != 0)
|
||||
return true;
|
||||
private:
|
||||
pthread_key_t key_;
|
||||
};
|
||||
|
||||
pthread_setspecific(log_recursion_key_, (void *) 1);
|
||||
return false;
|
||||
}
|
||||
// Check if non-main task is already in recursion (via TLS)
|
||||
inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; }
|
||||
|
||||
inline void HOT reset_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
main_task_recursion_guard_ = false;
|
||||
return;
|
||||
}
|
||||
// Create RAII guard for non-main task recursion
|
||||
inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); }
|
||||
|
||||
pthread_setspecific(log_recursion_key_, (void *) 0);
|
||||
}
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// LibreTiny doesn't have FreeRTOS TLS, so use a simple approach:
|
||||
// - Main task uses dedicated boolean (same as ESP32)
|
||||
@@ -466,29 +490,11 @@ class Logger : public Component {
|
||||
// - Cross-task "recursion" is prevented by the buffer mutex anyway
|
||||
// - Missing a recursive call from another task is acceptable (falls back to direct output)
|
||||
|
||||
inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
const bool was_recursive = main_task_recursion_guard_;
|
||||
main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
}
|
||||
// Check if non-main task is already in recursion
|
||||
inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; }
|
||||
|
||||
// For non-main tasks, use a simple shared guard
|
||||
// This may block legitimate concurrent logs from different tasks,
|
||||
// but that's acceptable - they'll fall back to direct console output
|
||||
const bool was_recursive = non_main_task_recursion_guard_;
|
||||
non_main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
}
|
||||
|
||||
inline void HOT reset_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
main_task_recursion_guard_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
non_main_task_recursion_guard_ = false;
|
||||
}
|
||||
// Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks)
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
Reference in New Issue
Block a user