mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 17:18:40 +00:00
[api] Add single-buffer write_raw_ overload for single-message path
The existing write_raw_ takes iovec array + count, but single-message writes (87-100% of traffic) always pass iovcnt=1. Adding a dedicated overload that takes (data, len) eliminates iovec construction, the iovcnt==1 branch, and pointer indirection on the hot path.
This commit is contained in:
@@ -112,8 +112,54 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
|
||||
}
|
||||
|
||||
// Write data to socket, overflow to backlog buffer if LWIP TCP send buffer is full.
|
||||
// Returns OK if all data was sent or successfully queued.
|
||||
// Returns SOCKET_WRITE_FAILED on hard error (sets state to FAILED).
|
||||
// Single-buffer write path — avoids iovec setup for the common single-message case.
|
||||
APIError APIFrameHelper::write_raw_(const void *data, uint16_t len) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
LOG_PACKET_SENDING(reinterpret_cast<const uint8_t *>(data), len);
|
||||
#endif
|
||||
|
||||
// Drain any existing backlog first
|
||||
if (!this->overflow_buf_.empty()) [[unlikely]] {
|
||||
APIError err = this->drain_overflow_and_handle_errors_();
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
}
|
||||
|
||||
// If backlog is clear, try direct send
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
ssize_t sent = this->socket_->write(data, len);
|
||||
|
||||
if (sent == -1) [[unlikely]] {
|
||||
int err = errno;
|
||||
if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) {
|
||||
HELPER_LOG("Socket write failed with errno %d", err);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
} else if (static_cast<uint16_t>(sent) >= len) [[likely]] {
|
||||
return APIError::OK;
|
||||
} else {
|
||||
// Partial write — queue remainder into overflow buffer
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
if (!this->overflow_buf_.enqueue_iov(&iov, 1, len, static_cast<uint16_t>(sent))) {
|
||||
HELPER_LOG("Overflow buffer full, dropping connection");
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Socket not ready — queue all data into overflow buffer
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
if (!this->overflow_buf_.enqueue_iov(&iov, 1, len, 0)) {
|
||||
HELPER_LOG("Overflow buffer full, dropping connection");
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// Multi-buffer write path for batched messages.
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
|
||||
@@ -190,7 +190,9 @@ class APIFrameHelper {
|
||||
// Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError drain_overflow_and_handle_errors_();
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
// Write a single contiguous buffer to the socket
|
||||
APIError write_raw_(const void *data, uint16_t len);
|
||||
// Write multiple iovec buffers to the socket in one writev call
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||
|
||||
// Check if a socket write errno is a hard error (not WOULD_BLOCK/EAGAIN).
|
||||
|
||||
@@ -499,11 +499,12 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuff
|
||||
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
uint8_t *buf_start = buffer.get_buffer()->data();
|
||||
struct iovec iov;
|
||||
aerr = this->encrypt_noise_message_(buffer.get_buffer()->data(), msg, iov);
|
||||
aerr = this->encrypt_noise_message_(buf_start, msg, iov);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
return this->write_raw_(&iov, 1, static_cast<uint16_t>(iov.iov_len));
|
||||
return this->write_raw_(iov.iov_base, static_cast<uint16_t>(iov.iov_len));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
@@ -538,12 +539,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
header[1] = (uint8_t) (len >> 8);
|
||||
header[2] = (uint8_t) len;
|
||||
|
||||
if (len == 0) {
|
||||
return this->write_raw_(header, 3); // Just header
|
||||
}
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return this->write_raw_(iov, 1, 3); // Just header
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
|
||||
@@ -205,7 +205,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
// Make sure to tell the remote that we don't
|
||||
// understand the indicator byte so it knows
|
||||
// we do not support it.
|
||||
struct iovec iov[1];
|
||||
// The \x00 first byte is the marker for plaintext.
|
||||
//
|
||||
// The remote will know how to handle the indicator byte,
|
||||
@@ -220,14 +219,12 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
"Bad indicator byte";
|
||||
char msg[INDICATOR_MSG_SIZE];
|
||||
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
|
||||
iov[0].iov_base = (void *) msg;
|
||||
this->write_raw_(msg, INDICATOR_MSG_SIZE);
|
||||
#else
|
||||
static const char MSG[] = "\x00"
|
||||
"Bad indicator byte";
|
||||
iov[0].iov_base = (void *) MSG;
|
||||
this->write_raw_(MSG, INDICATOR_MSG_SIZE);
|
||||
#endif
|
||||
iov[0].iov_len = INDICATOR_MSG_SIZE;
|
||||
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
@@ -294,9 +291,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWrite
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
uint8_t *msg_start = write_plaintext_header(buffer_data, msg, frame_header_padding_);
|
||||
uint8_t msg_header_len = static_cast<uint8_t>(buffer_data + frame_header_padding_ - msg_start);
|
||||
size_t msg_len = static_cast<size_t>(msg_header_len + msg.payload_size);
|
||||
struct iovec iov = {msg_start, msg_len};
|
||||
return write_raw_(&iov, 1, static_cast<uint16_t>(msg_len));
|
||||
uint16_t msg_len = static_cast<uint16_t>(msg_header_len + msg.payload_size);
|
||||
return write_raw_(msg_start, msg_len);
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer,
|
||||
|
||||
Reference in New Issue
Block a user