[api] Add single-buffer write_raw_slow_ overload for cold paths

Cold paths (write_frame_, bad indicator) no longer need to construct
an iovec just to call the slow path. The single-buffer overload wraps
in iovec internally, keeping call sites clean and avoiding iovec
setup in the caller's stack frame.
This commit is contained in:
J. Nick Koston
2026-03-29 11:12:10 -10:00
parent 5a8e54301f
commit ad25e2ae0c
4 changed files with 16 additions and 15 deletions
+7 -2
View File
@@ -112,8 +112,13 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
return APIError::OK;
}
// Write data to socket, overflow to backlog buffer if LWIP TCP send buffer is full.
// Slow path: handles partial writes, errors, and overflow buffering.
// Single-buffer slow path: wraps data in iovec and delegates to the iovec slow path.
APIError APIFrameHelper::write_raw_slow_(const void *data, uint16_t len, ssize_t sent) {
struct iovec iov = {const_cast<void *>(data), len};
return this->write_raw_slow_(&iov, 1, len, sent);
}
// Multi-buffer slow path: handles partial writes, errors, and overflow buffering.
// Called when the inline fast path in the header couldn't complete the write.
// sent == -1 means either the fast path write returned -1, or there was overflow backlog.
APIError APIFrameHelper::write_raw_slow_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent) {
+3 -3
View File
@@ -199,8 +199,7 @@ class APIFrameHelper {
if (sent == static_cast<ssize_t>(len)) [[likely]]
return APIError::OK;
}
struct iovec iov = {const_cast<void *>(data), len};
return this->write_raw_slow_(&iov, 1, len, sent);
return this->write_raw_slow_(data, len, sent);
}
// Write multiple iovec buffers to the socket (inlined fast path)
inline APIError ESPHOME_ALWAYS_INLINE write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
@@ -213,7 +212,8 @@ class APIFrameHelper {
}
return this->write_raw_slow_(iov, iovcnt, total_write_len, sent);
}
// Slow path (out-of-line): handle partial writes, errors, overflow buffering
// Slow paths (out-of-line): handle partial writes, errors, overflow buffering
APIError write_raw_slow_(const void *data, uint16_t len, ssize_t sent);
APIError write_raw_slow_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent);
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
@@ -539,13 +539,13 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
header[1] = (uint8_t) (len >> 8);
header[2] = (uint8_t) len;
// Handshake-only path — use slow path directly to avoid inlining write_raw_ fast path
if (len == 0) {
return this->write_raw_slow_(header, 3, -1);
}
// Handshake path — use iovec slow path directly to avoid inlining write_raw_ fast path
struct iovec iov[2];
iov[0].iov_base = header;
iov[0].iov_len = 3;
if (len == 0) {
return this->write_raw_slow_(iov, 1, 3, -1);
}
iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len;
@@ -219,15 +219,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
"Bad indicator byte";
char msg[INDICATOR_MSG_SIZE];
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
// Error path — use slow path directly to avoid inlining write_raw_ fast path
struct iovec iov = {msg, INDICATOR_MSG_SIZE};
this->write_raw_slow_(&iov, 1, INDICATOR_MSG_SIZE, -1);
this->write_raw_slow_(msg, INDICATOR_MSG_SIZE, -1);
#else
static const char MSG[] = "\x00"
"Bad indicator byte";
// Error path — use slow path directly to avoid inlining write_raw_ fast path
struct iovec iov = {const_cast<char *>(MSG), INDICATOR_MSG_SIZE};
this->write_raw_slow_(&iov, 1, INDICATOR_MSG_SIZE, -1);
this->write_raw_slow_(MSG, INDICATOR_MSG_SIZE, -1);
#endif
}
return aerr;