From df03a3700bf3b7bf31234cb854d1358008083de2 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Tue, 5 May 2026 10:09:25 -0600 Subject: [PATCH] Use recv/send single-buffer syscalls when iovec_count == 1 When the speculative read/write only has one buffer, dispatch to recv()/ send() instead of readv()/writev()/sendmsg() so the kernel can skip the iov_iter setup. The single-buffer path is the common case in HTTP and streaming workloads. The single-buffer write goes through a new write_policy::write_one backend hook so each reactor backend picks the right SIGPIPE strategy: - epoll: send(MSG_NOSIGNAL) - kqueue: write() -- macOS lacks MSG_NOSIGNAL; SIGPIPE is suppressed by the mandatory SO_NOSIGPIPE set in accept_policy and set_fd_options - select: send(MSG_NOSIGNAL) where defined, else write() with SO_NOSIGPIPE as the SIGPIPE fallback The single-buffer read uses recv() inline -- flag=0 is portable across all reactor platforms, so no read_policy is needed. --- .../native/detail/epoll/epoll_traits.hpp | 12 ++++++ .../native/detail/kqueue/kqueue_traits.hpp | 15 ++++++++ .../detail/reactor/reactor_stream_socket.hpp | 37 +++++++++++++++---- .../native/detail/select/select_traits.hpp | 20 ++++++++++ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/include/boost/corosio/native/detail/epoll/epoll_traits.hpp b/include/boost/corosio/native/detail/epoll/epoll_traits.hpp index 8938abfc..8230bf7a 100644 --- a/include/boost/corosio/native/detail/epoll/epoll_traits.hpp +++ b/include/boost/corosio/native/detail/epoll/epoll_traits.hpp @@ -74,6 +74,18 @@ struct epoll_traits while (n < 0 && errno == EINTR); return n; } + + static ssize_t write_one( + int fd, void const* data, std::size_t size) noexcept + { + ssize_t n; + do + { + n = ::send(fd, data, size, MSG_NOSIGNAL); + } + while (n < 0 && errno == EINTR); + return n; + } }; struct accept_policy diff --git a/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp b/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp index 3b22e044..4a734ed7 100644 --- a/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp +++ b/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp @@ -110,6 +110,21 @@ struct kqueue_traits while (n < 0 && errno == EINTR); return n; } + + // Single-buffer fast path. macOS lacks MSG_NOSIGNAL; SIGPIPE is + // suppressed by the mandatory SO_NOSIGPIPE set in accept_policy + // and set_fd_options, so plain write() is safe here. + static ssize_t write_one( + int fd, void const* data, std::size_t size) noexcept + { + ssize_t n; + do + { + n = ::write(fd, data, size); + } + while (n < 0 && errno == EINTR); + return n; + } }; struct accept_policy diff --git a/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp b/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp index d96e350e..406a52c7 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp @@ -394,13 +394,25 @@ reactor_stream_socketfd_, op.iovecs, op.iovec_count); + do + { + n = ::recv(this->fd_, bufs[0].data(), bufs[0].size(), 0); + } + while (n < 0 && errno == EINTR); + } + else + { + do + { + n = ::readv(this->fd_, op.iovecs, op.iovec_count); + } + while (n < 0 && errno == EINTR); } - while (n < 0 && errno == EINTR); if (n >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { @@ -490,9 +502,20 @@ reactor_stream_socketfd_, op.iovecs, op.iovec_count); + // Speculative write; the single-buffer case dispatches to a + // backend-specific fast path so the kernel skips msghdr/iov_iter + // setup (and so each backend can pick the right SIGPIPE strategy). + ssize_t n; + if (op.iovec_count == 1) + { + n = WriteOp::write_policy::write_one( + this->fd_, bufs[0].data(), bufs[0].size()); + } + else + { + n = WriteOp::write_policy::write( + this->fd_, op.iovecs, op.iovec_count); + } if (n >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { diff --git a/include/boost/corosio/native/detail/select/select_traits.hpp b/include/boost/corosio/native/detail/select/select_traits.hpp index 252e226f..56ad8d6a 100644 --- a/include/boost/corosio/native/detail/select/select_traits.hpp +++ b/include/boost/corosio/native/detail/select/select_traits.hpp @@ -84,6 +84,26 @@ struct select_traits while (n < 0 && errno == EINTR); return n; } + + // Single-buffer fast path. Where MSG_NOSIGNAL exists we use + // send() to suppress SIGPIPE inline; otherwise fall back to + // write() and rely on the SO_NOSIGPIPE set in accept_policy + // and set_fd_options. + static ssize_t write_one( + int fd, void const* data, std::size_t size) noexcept + { + ssize_t n; + do + { +#ifdef MSG_NOSIGNAL + n = ::send(fd, data, size, MSG_NOSIGNAL); +#else + n = ::write(fd, data, size); +#endif + } + while (n < 0 && errno == EINTR); + return n; + } }; struct accept_policy