From 5f2ad82a67541d9a1a8f41ce3449c4848791800b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 14:10:11 -1000 Subject: [PATCH] [api] Use named sentinels to avoid redundant syscall on write failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WRITE_NOT_ATTEMPTED (-1): cold path, no write tried yet — try write WRITE_FAILED (-2): fast path write() returned -1 — skip retry, check errno This avoids a redundant write() syscall when the fast path already got EWOULDBLOCK and fell through to the slow path. --- esphome/components/api/api_frame_helper.cpp | 35 +++++++++++---------- esphome/components/api/api_frame_helper.h | 26 ++++++++++----- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index a8d0ec9d71..e3b8fd2804 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -119,26 +119,27 @@ APIError APIFrameHelper::write_raw_buf_(const void *data, uint16_t len, ssize_t } // Handles partial writes, errors, and overflow buffering. -// Called when the inline fast path in the header couldn't complete the write, +// Called when the inline fast path couldn't complete the write, // or directly from cold paths (handshake, error handling). -// sent == -1 means either the fast path write returned -1, or there was overflow backlog. APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent) { - if (sent == -1) { - // Either the fast path write returned -1, or we were called directly (cold path) - if (!this->overflow_buf_.empty()) { - // Drain existing backlog first - APIError err = this->drain_overflow_and_handle_errors_(); - if (err != APIError::OK) - return err; + if (sent <= 0) { + if (sent == WRITE_NOT_ATTEMPTED) { + // Cold path: no write attempted yet, drain overflow and try + if (!this->overflow_buf_.empty()) { + APIError err = this->drain_overflow_and_handle_errors_(); + if (err != APIError::OK) + return err; + } + 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(total_write_len)) + return APIError::OK; + // Partial write or -1: fall through to error check / enqueue below + } } - // Try write if backlog is clear (either was empty, or drain succeeded) - 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(total_write_len)) - return APIError::OK; - // Partial write or -1: fall through to error check / enqueue below - } - if (sent == -1) { + // WRITE_FAILED or write above returned -1: check errno + if (sent == WRITE_FAILED || sent == -1) { int err = errno; if (err != EWOULDBLOCK && err != EAGAIN) { this->state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 94007eeb3d..35047e68d0 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -192,32 +192,42 @@ class APIFrameHelper { // Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors. APIError drain_overflow_and_handle_errors_(); + // Sentinel values for the sent parameter in write_raw_ methods + static constexpr ssize_t WRITE_NOT_ATTEMPTED = -1; // Cold path: no write attempted yet + static constexpr ssize_t WRITE_FAILED = -2; // Fast path: write() returned -1 + // Inlined write methods — used by hot paths (write_protobuf_packet, write_protobuf_messages) // These inline the fast path (overflow empty + full write) and tail-call the out-of-line // slow path only on failure/partial write. inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_buf_(const void *data, uint16_t len) { - ssize_t sent = -1; if (this->overflow_buf_.empty()) [[likely]] { - sent = this->socket_->write(data, len); + ssize_t sent = this->socket_->write(data, len); if (sent == static_cast(len)) [[likely]] return APIError::OK; + if (sent == -1) + return this->write_raw_buf_(data, len, WRITE_FAILED); + return this->write_raw_buf_(data, len, sent); } - return this->write_raw_buf_(data, len, sent); + return this->write_raw_buf_(data, len, WRITE_NOT_ATTEMPTED); } inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { - ssize_t sent = -1; if (this->overflow_buf_.empty()) [[likely]] { - sent = this->socket_->writev(iov, iovcnt); + ssize_t sent = this->socket_->writev(iov, iovcnt); if (sent == static_cast(total_write_len)) [[likely]] return APIError::OK; + if (sent == -1) + return this->write_raw_iov_(iov, iovcnt, total_write_len, WRITE_FAILED); + return this->write_raw_iov_(iov, iovcnt, total_write_len, sent); } - return this->write_raw_iov_(iov, iovcnt, total_write_len, sent); + return this->write_raw_iov_(iov, iovcnt, total_write_len, WRITE_NOT_ATTEMPTED); } // Out-of-line write paths: handle partial writes, errors, overflow buffering - APIError write_raw_buf_(const void *data, uint16_t len, ssize_t sent = -1); - APIError write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent = -1); + // sent: WRITE_NOT_ATTEMPTED (cold path), WRITE_FAILED (fast path write returned -1), or bytes sent (partial write) + APIError write_raw_buf_(const void *data, uint16_t len, ssize_t sent = WRITE_NOT_ATTEMPTED); + APIError write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, + ssize_t sent = WRITE_NOT_ATTEMPTED); // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit) std::unique_ptr socket_;