mirror of
https://github.com/esphome/esphome.git
synced 2026-09-25 22:10:21 +00:00
[api] Use named sentinels to avoid redundant syscall on write failure
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.
This commit is contained in:
@@ -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<ssize_t>(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<ssize_t>(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;
|
||||
|
||||
@@ -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<ssize_t>(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<ssize_t>(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::Socket> socket_;
|
||||
|
||||
Reference in New Issue
Block a user