ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
task_log_buffer_host.cpp
Go to the documentation of this file.
1#ifdef USE_HOST
2
4
5#ifdef USE_ESPHOME_TASK_LOG_BUFFER
6
7#include "esphome/core/log.h"
8#include <algorithm>
9#include <cstdio>
10
11namespace esphome::logger {
12
13TaskLogBuffer::TaskLogBuffer(size_t slot_count) : slot_count_(slot_count) {
14 // Allocate message slots
15 this->slots_ = std::make_unique<LogMessage[]>(slot_count);
16}
17
18TaskLogBuffer::~TaskLogBuffer() {
19 // unique_ptr handles cleanup automatically
20}
21
22int TaskLogBuffer::acquire_write_slot_() {
23 // Try to reserve a slot using compare-and-swap
24 size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed);
25
26 while (true) {
27 // Calculate next index (with wrap-around)
28 size_t next_reserve = (current_reserve + 1) % this->slot_count_;
29
30 // Check if buffer would be full
31 // Buffer is full when next write position equals read position
32 size_t current_read = this->read_index_.load(std::memory_order_acquire);
33 if (next_reserve == current_read) {
34 return -1; // Buffer full
35 }
36
37 // Try to claim this slot
38 if (this->reserve_index_.compare_exchange_weak(current_reserve, next_reserve, std::memory_order_acq_rel,
39 std::memory_order_relaxed)) {
40 return static_cast<int>(current_reserve);
41 }
42 // If CAS failed, current_reserve was updated, retry with new value
43 }
44}
45
46void TaskLogBuffer::commit_write_slot_(int slot_index) {
47 // Mark the slot as ready for reading
48 this->slots_[slot_index].ready.store(true, std::memory_order_release);
49
50 // Try to advance the write_index if we're the next expected commit
51 // This ensures messages are read in order
52 size_t expected = slot_index;
53 size_t next = (slot_index + 1) % this->slot_count_;
54
55 // We only advance write_index if this slot is the next one expected
56 // This handles out-of-order commits correctly
57 while (true) {
58 if (!this->write_index_.compare_exchange_weak(expected, next, std::memory_order_release,
59 std::memory_order_relaxed)) {
60 // Someone else advanced it or we're not next in line, that's fine
61 break;
62 }
63
64 // Successfully advanced, check if next slot is also ready
65 expected = next;
66 next = (next + 1) % this->slot_count_;
67 if (!this->slots_[expected].ready.load(std::memory_order_acquire)) {
68 break;
69 }
70 }
71}
72
73bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
74 const char *format, va_list args) {
75 // Acquire a slot
76 int slot_index = this->acquire_write_slot_();
77 if (slot_index < 0) {
78 return false; // Buffer full
79 }
80
81 LogMessage &msg = this->slots_[slot_index];
82
83 // Fill in the message header
84 msg.level = level;
85 msg.tag = tag;
86 msg.line = line;
87
88 // Store the thread name now to avoid crashes if thread exits before processing
89 if (thread_name != nullptr) {
90 strncpy(msg.thread_name, thread_name, sizeof(msg.thread_name) - 1);
91 msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
92 } else {
93 msg.thread_name[0] = '\0';
94 }
95
96 // Format the message text
97 int ret = vsnprintf(msg.text, sizeof(msg.text), format, args);
98 if (ret < 0) {
99 // Formatting error - still commit the slot but with empty text
100 msg.text[0] = '\0';
101 msg.text_length = 0;
102 } else {
103 msg.text_length = static_cast<uint16_t>(std::min(static_cast<size_t>(ret), sizeof(msg.text) - 1));
104 }
105
106 // Remove trailing newlines
107 while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') {
108 msg.text_length--;
109 }
110 msg.text[msg.text_length] = '\0';
111
112 // Commit the slot
113 this->commit_write_slot_(slot_index);
114
115 return true;
116}
117
118bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) {
119 size_t current_read = this->read_index_.load(std::memory_order_relaxed);
120 size_t current_write = this->write_index_.load(std::memory_order_acquire);
121
122 // Check if buffer is empty
123 if (current_read == current_write) {
124 return false;
125 }
126
127 // Check if the slot is ready (should always be true if write_index advanced)
128 LogMessage &msg = this->slots_[current_read];
129 if (!msg.ready.load(std::memory_order_acquire)) {
130 return false;
131 }
132
133 message = &msg;
134 text_length = msg.text_length;
135 return true;
136}
137
138void TaskLogBuffer::release_message_main_loop() {
139 size_t current_read = this->read_index_.load(std::memory_order_relaxed);
140
141 // Clear the ready flag
142 this->slots_[current_read].ready.store(false, std::memory_order_release);
143
144 // Advance read index
145 size_t next_read = (current_read + 1) % this->slot_count_;
146 this->read_index_.store(next_read, std::memory_order_release);
147}
148
149} // namespace esphome::logger
150
151#endif // USE_ESPHOME_TASK_LOG_BUFFER
152#endif // USE_HOST
TaskLogBuffer(size_t total_buffer_size)
const char * message
Definition component.cpp:38