mirror of
https://github.com/esphome/esphome.git
synced 2026-09-01 18:46:02 +00:00
[api] Inline write_raw_ fast paths into header with single slow path
Move the happy path (overflow empty + full write succeeds) inline into the header so it gets inlined at each call site. Both overloads share a single out-of-line write_raw_slow_ that handles partial writes, errors, and overflow buffering.
This commit is contained in:
@@ -112,89 +112,49 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
|
||||
}
|
||||
|
||||
// Write data to socket, overflow to backlog buffer if LWIP TCP send buffer is full.
|
||||
// Slow path: queue data into overflow buffer when socket can't accept it all.
|
||||
// Out-of-line to keep the fast paths small.
|
||||
APIError __attribute__((noinline))
|
||||
APIFrameHelper::enqueue_overflow_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t skip) {
|
||||
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, skip)) {
|
||||
HELPER_LOG("Overflow buffer full, dropping connection");
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// 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
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
return this->enqueue_overflow_(&iov, 1, len, static_cast<uint16_t>(sent));
|
||||
}
|
||||
}
|
||||
|
||||
// Socket not ready — queue all data
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
return this->enqueue_overflow_(&iov, 1, len, 0);
|
||||
}
|
||||
|
||||
// Multi-buffer write path for batched messages.
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
// 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) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_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_->writev(iov, iovcnt);
|
||||
|
||||
if (sent == -1) [[unlikely]] {
|
||||
if (sent == -1) {
|
||||
// Either the fast path got -1, or we were called with overflow backlog
|
||||
if (!this->overflow_buf_.empty()) {
|
||||
// Drain existing backlog first
|
||||
APIError err = this->drain_overflow_and_handle_errors_();
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
// Try again after drain
|
||||
if (this->overflow_buf_.empty()) {
|
||||
sent =
|
||||
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||
if (sent == static_cast<ssize_t>(total_write_len))
|
||||
return APIError::OK;
|
||||
}
|
||||
}
|
||||
if (sent == -1) {
|
||||
int err = errno;
|
||||
if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) {
|
||||
if (err != EWOULDBLOCK && err != EAGAIN) {
|
||||
this->state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", err);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
} else if (static_cast<uint16_t>(sent) >= total_write_len) [[likely]] {
|
||||
return APIError::OK;
|
||||
} else {
|
||||
return this->enqueue_overflow_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
|
||||
sent = 0; // Treat WOULD_BLOCK as zero bytes sent
|
||||
}
|
||||
}
|
||||
|
||||
// Socket not ready — queue all data
|
||||
return this->enqueue_overflow_(iov, iovcnt, total_write_len, 0);
|
||||
// Queue unsent data into overflow buffer
|
||||
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_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;
|
||||
}
|
||||
|
||||
const char *APIFrameHelper::get_peername_to(std::span<char, socket::SOCKADDR_STR_LEN> buf) const {
|
||||
|
||||
@@ -190,21 +190,33 @@ class APIFrameHelper {
|
||||
// Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError drain_overflow_and_handle_errors_();
|
||||
|
||||
// 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);
|
||||
// Slow path: queue unsent data into overflow buffer
|
||||
APIError enqueue_overflow_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t skip);
|
||||
|
||||
// Check if a socket write errno is a hard error (not WOULD_BLOCK/EAGAIN).
|
||||
// Returns WOULD_BLOCK for transient errors, SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError check_socket_write_err_(int err) {
|
||||
if (err == EWOULDBLOCK || err == EAGAIN)
|
||||
return APIError::WOULD_BLOCK;
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
// Write a single contiguous buffer to the socket (inlined fast path)
|
||||
inline APIError ESPHOME_ALWAYS_INLINE write_raw_(const void *data, uint16_t len) {
|
||||
// Fast path: no overflow backlog and full write succeeds
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
ssize_t sent = this->socket_->write(data, len);
|
||||
if (sent == static_cast<ssize_t>(len)) [[likely]]
|
||||
return APIError::OK;
|
||||
// Slow path: wrap in iovec and handle error/overflow
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
return this->write_raw_slow_(&iov, 1, len, sent);
|
||||
}
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
return this->write_raw_slow_(&iov, 1, len, -1);
|
||||
}
|
||||
// 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) {
|
||||
// Fast path: no overflow backlog and full writev succeeds
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||
if (sent == static_cast<ssize_t>(total_write_len)) [[likely]]
|
||||
return APIError::OK;
|
||||
return this->write_raw_slow_(iov, iovcnt, total_write_len, sent);
|
||||
}
|
||||
return this->write_raw_slow_(iov, iovcnt, total_write_len, -1);
|
||||
}
|
||||
// Slow path (out-of-line): handle partial writes, errors, overflow buffering
|
||||
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)
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
Reference in New Issue
Block a user