ESPHome 2026.1.4
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
13TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) {
14 // Allocate message slots
15 this->slots_ = std::make_unique<LogMessage[]>(slot_count);
16}
17
19 // unique_ptr handles cleanup automatically
20}
21
22int TaskLogBufferHost::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 TaskLogBufferHost::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 TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format,
74 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 // Get thread name using pthread
89 char thread_name_buf[LogMessage::MAX_THREAD_NAME_SIZE];
90 // pthread_getname_np works the same on Linux and macOS
91 if (pthread_getname_np(pthread_self(), thread_name_buf, sizeof(thread_name_buf)) == 0) {
92 strncpy(msg.thread_name, thread_name_buf, sizeof(msg.thread_name) - 1);
93 msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
94 } else {
95 msg.thread_name[0] = '\0';
96 }
97
98 // Format the message text
99 int ret = vsnprintf(msg.text, sizeof(msg.text), format, args);
100 if (ret < 0) {
101 // Formatting error - still commit the slot but with empty text
102 msg.text[0] = '\0';
103 msg.text_length = 0;
104 } else {
105 msg.text_length = static_cast<uint16_t>(std::min(static_cast<size_t>(ret), sizeof(msg.text) - 1));
106 }
107
108 // Remove trailing newlines
109 while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') {
110 msg.text_length--;
111 }
112 msg.text[msg.text_length] = '\0';
113
114 // Commit the slot
115 this->commit_write_slot_(slot_index);
116
117 return true;
118}
119
121 if (message == nullptr) {
122 return false;
123 }
124
125 size_t current_read = this->read_index_.load(std::memory_order_relaxed);
126 size_t current_write = this->write_index_.load(std::memory_order_acquire);
127
128 // Check if buffer is empty
129 if (current_read == current_write) {
130 return false;
131 }
132
133 // Check if the slot is ready (should always be true if write_index advanced)
134 LogMessage &msg = this->slots_[current_read];
135 if (!msg.ready.load(std::memory_order_acquire)) {
136 return false;
137 }
138
139 *message = &msg;
140 return true;
141}
142
144 size_t current_read = this->read_index_.load(std::memory_order_relaxed);
145
146 // Clear the ready flag
147 this->slots_[current_read].ready.store(false, std::memory_order_release);
148
149 // Advance read index
150 size_t next_read = (current_read + 1) % this->slot_count_;
151 this->read_index_.store(next_read, std::memory_order_release);
152}
153
154} // namespace esphome::logger
155
156#endif // USE_ESPHOME_TASK_LOG_BUFFER
157#endif // USE_HOST
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, va_list args)
TaskLogBufferHost(size_t slot_count)
Constructor that takes the number of message slots.
bool get_message_main_loop(LogMessage **message)
const char * message
Definition component.cpp:38