100.00% Lines (47/47) 100.00% Functions (13/13)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP
12   12  
13   #include <boost/corosio/detail/platform.hpp> 13   #include <boost/corosio/detail/platform.hpp>
14   14  
15   #if BOOST_COROSIO_HAS_EPOLL 15   #if BOOST_COROSIO_HAS_EPOLL
16   16  
17   #include <boost/corosio/native/detail/make_err.hpp> 17   #include <boost/corosio/native/detail/make_err.hpp>
18   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp> 18   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19   19  
20   #include <system_error> 20   #include <system_error>
21   #include <tuple> 21   #include <tuple>
22   22  
23   #include <errno.h> 23   #include <errno.h>
24   #include <netinet/in.h> 24   #include <netinet/in.h>
25   #include <sys/socket.h> 25   #include <sys/socket.h>
  26 + #include <sys/uio.h>
  27 + #include <unistd.h>
26   28  
27   /* epoll backend traits. 29   /* epoll backend traits.
28   30  
29   Captures the platform-specific behavior of the Linux epoll backend: 31   Captures the platform-specific behavior of the Linux epoll backend:
30   atomic SOCK_NONBLOCK|SOCK_CLOEXEC on socket(), accept4() for 32   atomic SOCK_NONBLOCK|SOCK_CLOEXEC on socket(), accept4() for
31   accepted connections, and sendmsg(MSG_NOSIGNAL) for writes. 33   accepted connections, and sendmsg(MSG_NOSIGNAL) for writes.
32   */ 34   */
33   35  
34   namespace boost::corosio::detail { 36   namespace boost::corosio::detail {
35   37  
36   class epoll_scheduler; 38   class epoll_scheduler;
37   39  
38   struct epoll_traits 40   struct epoll_traits
39   { 41   {
40   using scheduler_type = epoll_scheduler; 42   using scheduler_type = epoll_scheduler;
41   using desc_state_type = reactor_descriptor_state; 43   using desc_state_type = reactor_descriptor_state;
42   44  
43   static constexpr bool needs_write_notification = false; 45   static constexpr bool needs_write_notification = false;
44   46  
45   // No extra per-socket state or lifecycle hooks needed for epoll. 47   // No extra per-socket state or lifecycle hooks needed for epoll.
46   struct stream_socket_hook 48   struct stream_socket_hook
47   { 49   {
HITCBC 48   191 std::error_code on_set_option( 50   191 std::error_code on_set_option(
49   int fd, 51   int fd,
50   int level, 52   int level,
51   int optname, 53   int optname,
52   void const* data, 54   void const* data,
53   std::size_t size) noexcept 55   std::size_t size) noexcept
54   { 56   {
HITCBC 55   191 if (::setsockopt( 57   191 if (::setsockopt(
HITCBC 56   191 fd, level, optname, data, static_cast<socklen_t>(size)) != 58   191 fd, level, optname, data, static_cast<socklen_t>(size)) !=
57   0) 59   0)
HITCBC 58   5 return make_err(errno); 60   5 return make_err(errno);
HITCBC 59   186 return {}; 61   186 return {};
60   } 62   }
HITCBC 61   23062 static void pre_shutdown(int) noexcept {} 63   23008 static void pre_shutdown(int) noexcept {}
HITCBC 62   7501 static void pre_destroy(int) noexcept {} 64   7483 static void pre_destroy(int) noexcept {}
63   }; 65   };
64   66  
65   struct write_policy 67   struct write_policy
66   { 68   {
HITCBC 67   75 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 69   75 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
68   { 70   {
HITCBC 69   75 msghdr msg{}; 71   75 msghdr msg{};
HITCBC 70   75 msg.msg_iov = iovecs; 72   75 msg.msg_iov = iovecs;
HITCBC 71   75 msg.msg_iovlen = static_cast<std::size_t>(count); 73   75 msg.msg_iovlen = static_cast<std::size_t>(count);
72   74  
73   ssize_t n; 75   ssize_t n;
74   do 76   do
75   { 77   {
HITCBC 76   76 n = ::sendmsg(fd, &msg, MSG_NOSIGNAL); 78   76 n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
77   } 79   }
HITCBC 78   76 while (n < 0 && errno == EINTR); 80   76 while (n < 0 && errno == EINTR);
HITCBC 79   75 return n; 81   75 return n;
80   } 82   }
81   83  
82   static ssize_t 84   static ssize_t
HITCBC 83   98687 write_one(int fd, void const* data, std::size_t size) noexcept 85   101261 write_one(int fd, void const* data, std::size_t size) noexcept
84   { 86   {
85   ssize_t n; 87   ssize_t n;
86   do 88   do
87   { 89   {
HITCBC 88   98688 n = ::send(fd, data, size, MSG_NOSIGNAL); 90   101262 n = ::send(fd, data, size, MSG_NOSIGNAL);
  91 + }
HITGNC   92 + 101262 while (n < 0 && errno == EINTR);
HITGNC   93 + 101261 return n;
  94 + }
  95 + };
  96 +
  97 + // Descriptors are not sockets: sendmsg() fails with ENOTSOCK on a
  98 + // pipe or character device, so the write path is writev()/write()
  99 + // and SIGPIPE suppression is structurally unavailable -- MSG_NOSIGNAL
  100 + // is a send() flag and SO_NOSIGPIPE a socket option. A write to a
  101 + // pipe whose read end has closed raises SIGPIPE, exactly as a plain
  102 + // write(2) would; callers install SIG_IGN.
  103 + struct descriptor_write_policy
  104 + {
HITGNC   105 + 2 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
  106 + {
  107 + ssize_t n;
  108 + do
  109 + {
HITGNC   110 + 2 n = ::writev(fd, iovecs, count);
  111 + }
HITGNC   112 + 2 while (n < 0 && errno == EINTR);
HITGNC   113 + 2 return n;
  114 + }
  115 +
  116 + // Single-buffer fast path: skips the kernel's iov_iter setup.
  117 + static ssize_t
HITGNC   118 + 2 write_one(int fd, void const* data, std::size_t size) noexcept
  119 + {
  120 + ssize_t n;
  121 + do
  122 + {
HITGNC   123 + 2 n = ::write(fd, data, size);
89   } 124   }
HITCBC 90   98688 while (n < 0 && errno == EINTR); 125   2 while (n < 0 && errno == EINTR);
HITCBC 91   98687 return n; 126   2 return n;
92   } 127   }
93   }; 128   };
94   129  
95   struct accept_policy 130   struct accept_policy
96   { 131   {
97   static int 132   static int
HITCBC 98   4970 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 133   4958 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
99   { 134   {
HITCBC 100   4970 addrlen = sizeof(peer); 135   4958 addrlen = sizeof(peer);
101   int new_fd; 136   int new_fd;
102   do 137   do
103   { 138   {
HITCBC 104   4971 new_fd = ::accept4( 139   4959 new_fd = ::accept4(
105   fd, reinterpret_cast<sockaddr*>(&peer), &addrlen, 140   fd, reinterpret_cast<sockaddr*>(&peer), &addrlen,
106   SOCK_NONBLOCK | SOCK_CLOEXEC); 141   SOCK_NONBLOCK | SOCK_CLOEXEC);
107   } 142   }
HITCBC 108   4971 while (new_fd < 0 && errno == EINTR); 143   4959 while (new_fd < 0 && errno == EINTR);
HITCBC 109   4970 return new_fd; 144   4958 return new_fd;
110   } 145   }
111   }; 146   };
112   147  
113   // Create a nonblocking, close-on-exec socket using Linux's atomic flags. 148   // Create a nonblocking, close-on-exec socket using Linux's atomic flags.
HITCBC 114   3132 static int create_socket(int family, int type, int protocol) noexcept 149   3126 static int create_socket(int family, int type, int protocol) noexcept
115   { 150   {
HITCBC 116   3132 return ::socket(family, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol); 151   3126 return ::socket(family, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol);
117   } 152   }
118   153  
119   // Apply protocol-specific options after socket creation. 154   // Apply protocol-specific options after socket creation.
120   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 155   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
HITCBC 121   2658 static std::error_code configure_ip_socket(int fd, int family) noexcept 156   2652 static std::error_code configure_ip_socket(int fd, int family) noexcept
122   { 157   {
HITCBC 123   2658 if (family == AF_INET6) 158   2652 if (family == AF_INET6)
124   { 159   {
HITCBC 125   23 int one = 1; 160   23 int one = 1;
126   std::ignore = 161   std::ignore =
HITCBC 127   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 162   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
128   } 163   }
HITCBC 129   2658 return {}; 164   2652 return {};
130   } 165   }
131   166  
132   // Apply protocol-specific options for acceptor sockets. 167   // Apply protocol-specific options for acceptor sockets.
133   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 168   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
HITCBC 134   349 static std::error_code configure_ip_acceptor(int fd, int family) noexcept 169   349 static std::error_code configure_ip_acceptor(int fd, int family) noexcept
135   { 170   {
HITCBC 136   349 if (family == AF_INET6) 171   349 if (family == AF_INET6)
137   { 172   {
HITCBC 138   11 int val = 0; 173   11 int val = 0;
139   std::ignore = 174   std::ignore =
HITCBC 140   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 175   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
141   } 176   }
HITCBC 142   349 return {}; 177   349 return {};
143   } 178   }
144   179  
145   // No extra configuration needed for local (unix) sockets on epoll. 180   // No extra configuration needed for local (unix) sockets on epoll.
HITCBC 146   116 static std::error_code configure_local_socket(int /*fd*/) noexcept 181   116 static std::error_code configure_local_socket(int /*fd*/) noexcept
147   { 182   {
HITCBC 148   116 return {}; 183   116 return {};
149   } 184   }
150   185  
151   // Non-mutating validation for fds adopted via assign(). Used when 186   // Non-mutating validation for fds adopted via assign(). Used when
152   // the caller retains fd ownership responsibility. 187   // the caller retains fd ownership responsibility.
HITCBC 153   168 static std::error_code validate_assigned_fd(int /*fd*/) noexcept 188   168 static std::error_code validate_assigned_fd(int /*fd*/) noexcept
154   { 189   {
HITCBC 155   168 return {}; 190   168 return {};
156   } 191   }
157   }; 192   };
158   193  
159   } // namespace boost::corosio::detail 194   } // namespace boost::corosio::detail
160   195  
161   #endif // BOOST_COROSIO_HAS_EPOLL 196   #endif // BOOST_COROSIO_HAS_EPOLL
162   197  
163   #endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP 198   #endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP