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_LOCAL_STREAM_SOCKET_HPP 10   #ifndef BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP
11   #define BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP 11   #define BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 16   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/detail/op_base.hpp> 17   #include <boost/corosio/detail/op_base.hpp>
18   #include <boost/corosio/io/io_stream.hpp> 18   #include <boost/corosio/io/io_stream.hpp>
19   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
20   #include <boost/corosio/detail/buffer_param.hpp> 20   #include <boost/corosio/detail/buffer_param.hpp>
21   #include <boost/corosio/local_endpoint.hpp> 21   #include <boost/corosio/local_endpoint.hpp>
22   #include <boost/corosio/local_stream.hpp> 22   #include <boost/corosio/local_stream.hpp>
23   #include <boost/corosio/shutdown_type.hpp> 23   #include <boost/corosio/shutdown_type.hpp>
24   #include <boost/corosio/wait_type.hpp> 24   #include <boost/corosio/wait_type.hpp>
25   #include <boost/capy/ex/executor_ref.hpp> 25   #include <boost/capy/ex/executor_ref.hpp>
26   #include <boost/capy/ex/execution_context.hpp> 26   #include <boost/capy/ex/execution_context.hpp>
27   #include <boost/capy/ex/io_env.hpp> 27   #include <boost/capy/ex/io_env.hpp>
28   #include <boost/capy/concept/executor.hpp> 28   #include <boost/capy/concept/executor.hpp>
29   29  
30   #include <system_error> 30   #include <system_error>
31   31  
32   #include <concepts> 32   #include <concepts>
33   #include <coroutine> 33   #include <coroutine>
34   #include <cstddef> 34   #include <cstddef>
35   #include <stop_token> 35   #include <stop_token>
36   #include <type_traits> 36   #include <type_traits>
37   37  
38   namespace boost::corosio { 38   namespace boost::corosio {
39   39  
40   /** An asynchronous Unix stream socket for coroutine I/O. 40   /** An asynchronous Unix stream socket for coroutine I/O.
41   41  
42   This class provides asynchronous Unix domain stream socket 42   This class provides asynchronous Unix domain stream socket
43   operations that return awaitable types. Each operation 43   operations that return awaitable types. Each operation
44   participates in the affine awaitable protocol, ensuring 44   participates in the affine awaitable protocol, ensuring
45   coroutines resume on the correct executor. 45   coroutines resume on the correct executor.
46   46  
47   The socket must be opened before performing I/O operations. 47   The socket must be opened before performing I/O operations.
48   Operations support cancellation through `std::stop_token` via 48   Operations support cancellation through `std::stop_token` via
49   the affine protocol, or explicitly through the `cancel()` 49   the affine protocol, or explicitly through the `cancel()`
50   member function. 50   member function.
51   51  
52   @par Thread Safety 52   @par Thread Safety
53   Distinct objects: Safe.@n 53   Distinct objects: Safe.@n
54   Shared objects: Unsafe. A socket must not have concurrent 54   Shared objects: Unsafe. A socket must not have concurrent
55   operations of the same type (e.g., two simultaneous reads). 55   operations of the same type (e.g., two simultaneous reads).
56   One read and one write may be in flight simultaneously. 56   One read and one write may be in flight simultaneously.
57   57  
58   @par Semantics 58   @par Semantics
59   Wraps the platform Unix domain socket stack. Operations 59   Wraps the platform Unix domain socket stack. Operations
60   dispatch to OS socket APIs via the io_context backend 60   dispatch to OS socket APIs via the io_context backend
61   (epoll, kqueue, select, or IOCP). Satisfies @ref capy::Stream. 61   (epoll, kqueue, select, or IOCP). Satisfies @ref capy::Stream.
62   62  
63   @par Example 63   @par Example
64   @code 64   @code
65   io_context ioc; 65   io_context ioc;
66 - s.open();  
67   local_stream_socket s(ioc); 66   local_stream_socket s(ioc);
68   67  
69   auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock")); 68   auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock"));
70   if (ec) 69   if (ec)
71   co_return; 70   co_return;
72   71  
73   char buf[1024]; 72   char buf[1024];
74   auto [read_ec, n] = co_await s.read_some( 73   auto [read_ec, n] = co_await s.read_some(
75   capy::mutable_buffer(buf, sizeof(buf))); 74   capy::mutable_buffer(buf, sizeof(buf)));
76   @endcode 75   @endcode
77   */ 76   */
78   class BOOST_COROSIO_DECL local_stream_socket : public io_stream 77   class BOOST_COROSIO_DECL local_stream_socket : public io_stream
79   { 78   {
80   public: 79   public:
81   /// The endpoint type used by this socket. 80   /// The endpoint type used by this socket.
82   using endpoint_type = corosio::local_endpoint; 81   using endpoint_type = corosio::local_endpoint;
83   82  
84   using shutdown_type = corosio::shutdown_type; 83   using shutdown_type = corosio::shutdown_type;
85   using enum corosio::shutdown_type; 84   using enum corosio::shutdown_type;
86   85  
87   /** Define backend hooks for local stream socket operations. 86   /** Define backend hooks for local stream socket operations.
88   87  
89   Platform backends (epoll, kqueue, select) derive from this 88   Platform backends (epoll, kqueue, select) derive from this
90   to implement socket I/O, connection, and option management. 89   to implement socket I/O, connection, and option management.
91   */ 90   */
92   struct implementation : io_stream::implementation 91   struct implementation : io_stream::implementation
93   { 92   {
94   /** Initiate an asynchronous connect to the given endpoint. 93   /** Initiate an asynchronous connect to the given endpoint.
95   94  
96   @param h Coroutine handle to resume on completion. 95   @param h Coroutine handle to resume on completion.
97   @param ex Executor for dispatching the completion. 96   @param ex Executor for dispatching the completion.
98   @param ep The local endpoint (path) to connect to. 97   @param ep The local endpoint (path) to connect to.
99   @param token Stop token for cancellation. 98   @param token Stop token for cancellation.
100   @param ec Output error code. 99   @param ec Output error code.
101   100  
102   @return Coroutine handle to resume immediately. 101   @return Coroutine handle to resume immediately.
103   */ 102   */
104   virtual std::coroutine_handle<> connect( 103   virtual std::coroutine_handle<> connect(
105   std::coroutine_handle<> h, 104   std::coroutine_handle<> h,
106   capy::executor_ref ex, 105   capy::executor_ref ex,
107   corosio::local_endpoint ep, 106   corosio::local_endpoint ep,
108   std::stop_token token, 107   std::stop_token token,
109   std::error_code* ec) = 0; 108   std::error_code* ec) = 0;
110   109  
111   /** Initiate an asynchronous wait for socket readiness. 110   /** Initiate an asynchronous wait for socket readiness.
112   111  
113   Completes when the socket becomes ready for the 112   Completes when the socket becomes ready for the
114   specified direction, or an error condition is 113   specified direction, or an error condition is
115   reported. No bytes are transferred. 114   reported. No bytes are transferred.
116   115  
117   @param h Coroutine handle to resume on completion. 116   @param h Coroutine handle to resume on completion.
118   @param ex Executor for dispatching the completion. 117   @param ex Executor for dispatching the completion.
119   @param w The direction to wait on. 118   @param w The direction to wait on.
120   @param token Stop token for cancellation. 119   @param token Stop token for cancellation.
121   @param ec Output error code. 120   @param ec Output error code.
122   121  
123   @return Coroutine handle to resume immediately. 122   @return Coroutine handle to resume immediately.
124   */ 123   */
125   virtual std::coroutine_handle<> wait( 124   virtual std::coroutine_handle<> wait(
126   std::coroutine_handle<> h, 125   std::coroutine_handle<> h,
127   capy::executor_ref ex, 126   capy::executor_ref ex,
128   wait_type w, 127   wait_type w,
129   std::stop_token token, 128   std::stop_token token,
130   std::error_code* ec) = 0; 129   std::error_code* ec) = 0;
131   130  
132   /** Shut down the socket for the given direction(s). 131   /** Shut down the socket for the given direction(s).
133   132  
134   @param what The shutdown direction. 133   @param what The shutdown direction.
135   134  
136   @return Error code on failure, empty on success. 135   @return Error code on failure, empty on success.
137   */ 136   */
138   virtual std::error_code shutdown(shutdown_type what) noexcept = 0; 137   virtual std::error_code shutdown(shutdown_type what) noexcept = 0;
139   138  
140   /// Return the platform socket descriptor. 139   /// Return the platform socket descriptor.
141   virtual native_handle_type native_handle() const noexcept = 0; 140   virtual native_handle_type native_handle() const noexcept = 0;
142   141  
143   /** Release ownership of the native socket handle. 142   /** Release ownership of the native socket handle.
144   143  
145   Deregisters the socket from the reactor without closing 144   Deregisters the socket from the reactor without closing
146   the descriptor. The caller takes ownership. 145   the descriptor. The caller takes ownership.
147   146  
148   @return The native handle. 147   @return The native handle.
149   */ 148   */
150   virtual native_handle_type release_socket() noexcept = 0; 149   virtual native_handle_type release_socket() noexcept = 0;
151   150  
152   /** Request cancellation of pending asynchronous operations. 151   /** Request cancellation of pending asynchronous operations.
153   152  
154   All outstanding operations complete with operation_canceled error. 153   All outstanding operations complete with operation_canceled error.
155   Check `ec == cond::canceled` for portable comparison. 154   Check `ec == cond::canceled` for portable comparison.
156   */ 155   */
157   virtual void cancel() noexcept = 0; 156   virtual void cancel() noexcept = 0;
158   157  
159   /** Set a socket option. 158   /** Set a socket option.
160   159  
161   @param level The protocol level (e.g. `SOL_SOCKET`). 160   @param level The protocol level (e.g. `SOL_SOCKET`).
162   @param optname The option name (e.g. `SO_KEEPALIVE`). 161   @param optname The option name (e.g. `SO_KEEPALIVE`).
163   @param data Pointer to the option value. 162   @param data Pointer to the option value.
164   @param size Size of the option value in bytes. 163   @param size Size of the option value in bytes.
165   @return Error code on failure, empty on success. 164   @return Error code on failure, empty on success.
166   */ 165   */
167   virtual std::error_code set_option( 166   virtual std::error_code set_option(
168   int level, 167   int level,
169   int optname, 168   int optname,
170   void const* data, 169   void const* data,
171   std::size_t size) noexcept = 0; 170   std::size_t size) noexcept = 0;
172   171  
173   /** Get a socket option. 172   /** Get a socket option.
174   173  
175   @param level The protocol level (e.g. `SOL_SOCKET`). 174   @param level The protocol level (e.g. `SOL_SOCKET`).
176   @param optname The option name (e.g. `SO_KEEPALIVE`). 175   @param optname The option name (e.g. `SO_KEEPALIVE`).
177   @param data Pointer to receive the option value. 176   @param data Pointer to receive the option value.
178   @param size On entry, the size of the buffer. On exit, 177   @param size On entry, the size of the buffer. On exit,
179   the size of the option value. 178   the size of the option value.
180   @return Error code on failure, empty on success. 179   @return Error code on failure, empty on success.
181   */ 180   */
182   virtual std::error_code 181   virtual std::error_code
183   get_option(int level, int optname, void* data, std::size_t* size) 182   get_option(int level, int optname, void* data, std::size_t* size)
184   const noexcept = 0; 183   const noexcept = 0;
185   184  
186   /// Return the cached local endpoint. 185   /// Return the cached local endpoint.
187   virtual corosio::local_endpoint local_endpoint() const noexcept = 0; 186   virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
188   187  
189   /// Return the cached remote endpoint. 188   /// Return the cached remote endpoint.
190   virtual corosio::local_endpoint remote_endpoint() const noexcept = 0; 189   virtual corosio::local_endpoint remote_endpoint() const noexcept = 0;
191   }; 190   };
192   191  
193   /// Represent the awaitable returned by @ref connect. 192   /// Represent the awaitable returned by @ref connect.
194   struct connect_awaitable 193   struct connect_awaitable
195   : detail::void_op_base<connect_awaitable> 194   : detail::void_op_base<connect_awaitable>
196   { 195   {
197   local_stream_socket& s_; 196   local_stream_socket& s_;
198   corosio::local_endpoint endpoint_; 197   corosio::local_endpoint endpoint_;
199   198  
HITCBC 200   21 connect_awaitable( 199   21 connect_awaitable(
201   local_stream_socket& s, corosio::local_endpoint ep) noexcept 200   local_stream_socket& s, corosio::local_endpoint ep) noexcept
HITCBC 202   21 : s_(s), endpoint_(ep) {} 201   21 : s_(s), endpoint_(ep) {}
203   202  
HITCBC 204   21 std::coroutine_handle<> dispatch( 203   21 std::coroutine_handle<> dispatch(
205   std::coroutine_handle<> h, capy::executor_ref ex) const 204   std::coroutine_handle<> h, capy::executor_ref ex) const
206   { 205   {
HITCBC 207   21 return s_.get().connect(h, ex, endpoint_, token_, &ec_); 206   21 return s_.get().connect(h, ex, endpoint_, token_, &ec_);
208   } 207   }
209   }; 208   };
210   209  
211   /// Represent the awaitable returned by @ref wait. 210   /// Represent the awaitable returned by @ref wait.
212   struct wait_awaitable 211   struct wait_awaitable
213   : detail::void_op_base<wait_awaitable> 212   : detail::void_op_base<wait_awaitable>
214   { 213   {
215   local_stream_socket& s_; 214   local_stream_socket& s_;
216   wait_type w_; 215   wait_type w_;
217   216  
HITCBC 218   10 wait_awaitable(local_stream_socket& s, wait_type w) noexcept 217   12 wait_awaitable(local_stream_socket& s, wait_type w) noexcept
HITCBC 219   10 : s_(s), w_(w) {} 218   12 : s_(s), w_(w) {}
220   219  
HITCBC 221   10 std::coroutine_handle<> dispatch( 220   12 std::coroutine_handle<> dispatch(
222   std::coroutine_handle<> h, capy::executor_ref ex) const 221   std::coroutine_handle<> h, capy::executor_ref ex) const
223   { 222   {
HITCBC 224   10 return s_.get().wait(h, ex, w_, token_, &ec_); 223   12 return s_.get().wait(h, ex, w_, token_, &ec_);
225   } 224   }
226   }; 225   };
227   226  
228   public: 227   public:
229   /** Destructor. 228   /** Destructor.
230   229  
231   Closes the socket if open, cancelling any pending operations. 230   Closes the socket if open, cancelling any pending operations.
232   */ 231   */
233   ~local_stream_socket() override; 232   ~local_stream_socket() override;
234   233  
235   /** Construct a socket from an execution context. 234   /** Construct a socket from an execution context.
236   235  
237   @param ctx The execution context that will own this socket. 236   @param ctx The execution context that will own this socket.
238   */ 237   */
239   explicit local_stream_socket(capy::execution_context& ctx); 238   explicit local_stream_socket(capy::execution_context& ctx);
240   239  
241   /** Construct a socket from an executor. 240   /** Construct a socket from an executor.
242   241  
243   The socket is associated with the executor's context. 242   The socket is associated with the executor's context.
244   243  
245   @param ex The executor whose context will own the socket. 244   @param ex The executor whose context will own the socket.
246   */ 245   */
247   template<class Ex> 246   template<class Ex>
248   requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_socket>) && 247   requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_socket>) &&
249   capy::Executor<Ex> 248   capy::Executor<Ex>
250   explicit local_stream_socket(Ex const& ex) : local_stream_socket(ex.context()) 249   explicit local_stream_socket(Ex const& ex) : local_stream_socket(ex.context())
251   { 250   {
252   } 251   }
253   252  
254   /** Move constructor. 253   /** Move constructor.
255   254  
256   Transfers ownership of the socket resources. 255   Transfers ownership of the socket resources.
257   256  
258   @param other The socket to move from. 257   @param other The socket to move from.
259   258  
260   @pre No awaitables returned by @p other's methods exist. 259   @pre No awaitables returned by @p other's methods exist.
261   @pre The execution context associated with @p other must 260   @pre The execution context associated with @p other must
262   outlive this socket. 261   outlive this socket.
263   */ 262   */
HITCBC 264   6 local_stream_socket(local_stream_socket&& other) noexcept 263   10 local_stream_socket(local_stream_socket&& other) noexcept
HITCBC 265   6 : io_object(std::move(other)) 264   10 : io_object(std::move(other))
266   { 265   {
HITCBC 267   6 } 266   10 }
268   267  
269   /** Move assignment operator. 268   /** Move assignment operator.
270   269  
271   Closes any existing socket and transfers ownership. 270   Closes any existing socket and transfers ownership.
272   271  
273   @param other The socket to move from. 272   @param other The socket to move from.
274   273  
275   @pre No awaitables returned by either `*this` or @p other's 274   @pre No awaitables returned by either `*this` or @p other's
276   methods exist. 275   methods exist.
277   @pre The execution context associated with @p other must 276   @pre The execution context associated with @p other must
278   outlive this socket. 277   outlive this socket.
279   278  
280   @return Reference to this socket. 279   @return Reference to this socket.
281   */ 280   */
HITCBC 282   4 local_stream_socket& operator=(local_stream_socket&& other) noexcept 281   4 local_stream_socket& operator=(local_stream_socket&& other) noexcept
283   { 282   {
HITCBC 284   4 if (this != &other) 283   4 if (this != &other)
285   { 284   {
HITCBC 286   2 close(); 285   2 close();
HITCBC 287   2 io_object::operator=(std::move(other)); 286   2 io_object::operator=(std::move(other));
288   } 287   }
HITCBC 289   4 return *this; 288   4 return *this;
290   } 289   }
291   290  
292   local_stream_socket(local_stream_socket const&) = delete; 291   local_stream_socket(local_stream_socket const&) = delete;
293   local_stream_socket& operator=(local_stream_socket const&) = delete; 292   local_stream_socket& operator=(local_stream_socket const&) = delete;
294   293  
295   /** Open the socket. 294   /** Open the socket.
296   295  
297   Creates a Unix stream socket and associates it with 296   Creates a Unix stream socket and associates it with
298   the platform reactor. 297   the platform reactor.
299   298  
  299 + Failures such as descriptor exhaustion are normal runtime
  300 + conditions and are reported through the returned error code.
  301 + Opening an already-open socket is a no-op that reports
  302 + success.
  303 +
300   @param proto The protocol. Defaults to local_stream{}. 304   @param proto The protocol. Defaults to local_stream{}.
301   305  
302 - @throws std::system_error on failure. 306 + @return The error code, empty on success.
303   */ 307   */
304 - void open(local_stream proto = {}); 308 + [[nodiscard]] std::error_code open(local_stream proto = {}) noexcept;
305   309  
306   /** Close the socket. 310   /** Close the socket.
307   311  
308   Releases socket resources. Any pending operations complete 312   Releases socket resources. Any pending operations complete
309   with `errc::operation_canceled`. 313   with `errc::operation_canceled`.
310   */ 314   */
311 - void close(); 315 + void close() noexcept;
312   316  
313   /** Check if the socket is open. 317   /** Check if the socket is open.
314   318  
315   @return `true` if the socket is open and ready for operations. 319   @return `true` if the socket is open and ready for operations.
316   */ 320   */
HITCBC 317   549 bool is_open() const noexcept 321   573 bool is_open() const noexcept
318   { 322   {
319   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 323   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
320   return h_ && get().native_handle() != ~native_handle_type(0); 324   return h_ && get().native_handle() != ~native_handle_type(0);
321   #else 325   #else
HITCBC 322   549 return h_ && get().native_handle() >= 0; 326   573 return h_ && get().native_handle() >= 0;
323   #endif 327   #endif
324   } 328   }
325   329  
326   /** Initiate an asynchronous connect operation. 330   /** Initiate an asynchronous connect operation.
327   331  
328   If the socket is not already open, it is opened automatically. 332   If the socket is not already open, it is opened automatically.
329   333  
330   @param ep The local endpoint (path) to connect to. 334   @param ep The local endpoint (path) to connect to.
331   335  
332   @return An awaitable that completes with io_result<>. 336   @return An awaitable that completes with io_result<>.
333   337  
334 - @throws std::system_error if the socket needs to be opened 338 + If the socket needs to be opened and the open fails, the
335 - and the open fails. 339 + awaitable completes immediately with that error.
336   */ 340   */
HITCBC 337 - 21 auto connect(corosio::local_endpoint ep) 341 + 21 [[nodiscard]] auto connect(corosio::local_endpoint ep)
338   { 342   {
HITGNC   343 + 21 connect_awaitable aw(*this, ep);
HITCBC 339   21 if (!is_open()) 344   21 if (!is_open())
HITCBC 340 - 13 open(); 345 + 13 aw.ec_ = open();
HITCBC 341 - 21 return connect_awaitable(*this, ep); 346 + 21 return aw;
342   } 347   }
343   348  
344   /** Wait for the socket to become ready in a given direction. 349   /** Wait for the socket to become ready in a given direction.
345   350  
346   Suspends until the socket is ready for the requested 351   Suspends until the socket is ready for the requested
347   direction, or an error condition is reported. No bytes 352   direction, or an error condition is reported. No bytes
348   are transferred. 353   are transferred.
349   354  
350   @param w The wait direction (read, write, or error). 355   @param w The wait direction (read, write, or error).
351   356  
352   @return An awaitable that completes with `io_result<>`. 357   @return An awaitable that completes with `io_result<>`.
353   358  
  359 + A closed socket completes with `errc::bad_file_descriptor`.
  360 +
354   @par Preconditions 361   @par Preconditions
355 - The socket must be open. This socket must outlive the 362 + This socket must outlive the returned awaitable.
356 - returned awaitable.  
357   */ 363   */
HITCBC 358   10 [[nodiscard]] auto wait(wait_type w) 364   12 [[nodiscard]] auto wait(wait_type w)
359   { 365   {
HITCBC 360   10 return wait_awaitable(*this, w); 366   12 return wait_awaitable(*this, w);
361   } 367   }
362   368  
363   /** Cancel any pending asynchronous operations. 369   /** Cancel any pending asynchronous operations.
364   370  
365   All outstanding operations complete with `errc::operation_canceled`. 371   All outstanding operations complete with `errc::operation_canceled`.
366   Check `ec == cond::canceled` for portable comparison. 372   Check `ec == cond::canceled` for portable comparison.
367   */ 373   */
368 - void cancel(); 374 + void cancel() noexcept;
369   375  
370   /** Get the native socket handle. 376   /** Get the native socket handle.
371   377  
372   Returns the underlying platform-specific socket descriptor. 378   Returns the underlying platform-specific socket descriptor.
373   On POSIX systems this is an `int` file descriptor. 379   On POSIX systems this is an `int` file descriptor.
374   380  
375   @return The native socket handle, or an invalid sentinel 381   @return The native socket handle, or an invalid sentinel
376   if not open. 382   if not open.
377   */ 383   */
378   native_handle_type native_handle() const noexcept; 384   native_handle_type native_handle() const noexcept;
379   385  
380   /** Query the number of bytes available for reading. 386   /** Query the number of bytes available for reading.
381   387  
382   @return The number of bytes that can be read without blocking. 388   @return The number of bytes that can be read without blocking.
383   389  
384 - @throws std::logic_error if the socket is not open. 390 + @throws std::system_error `errc::bad_file_descriptor` if the
385 - @throws std::system_error on ioctl failure. 391 + socket is not open; otherwise thrown on ioctl failure.
386   */ 392   */
387   std::size_t available() const; 393   std::size_t available() const;
388   394  
389   /** Release ownership of the native socket handle. 395   /** Release ownership of the native socket handle.
390   396  
391   Deregisters the socket from the backend and cancels pending 397   Deregisters the socket from the backend and cancels pending
392   operations without closing the descriptor. The caller takes 398   operations without closing the descriptor. The caller takes
393   ownership of the returned handle. 399   ownership of the returned handle.
394   400  
395   @return The native handle. 401   @return The native handle.
396   402  
397 - @throws std::logic_error if the socket is not open. 403 + @throws std::system_error `errc::bad_file_descriptor` if the
  404 + socket is not open.
398   405  
399   @post is_open() == false 406   @post is_open() == false
400   */ 407   */
401   native_handle_type release(); 408   native_handle_type release();
402   409  
403   /** Disable sends or receives on the socket. 410   /** Disable sends or receives on the socket.
404   411  
405   Unix stream connections are full-duplex: each direction 412   Unix stream connections are full-duplex: each direction
406   (send and receive) operates independently. This function 413   (send and receive) operates independently. This function
407   allows you to close one or both directions without 414   allows you to close one or both directions without
408   destroying the socket. 415   destroying the socket.
409   416  
  417 + Failures such as a peer that already disconnected are
  418 + normal runtime conditions and are reported through the
  419 + returned error code. A closed socket reports
  420 + `errc::bad_file_descriptor`.
  421 +
410   @param what Determines what operations will no longer 422   @param what Determines what operations will no longer
411   be allowed. 423   be allowed.
412   424  
413 - @throws std::system_error on failure. 425 + @return The error code, empty on success.
414 - */  
415 - void shutdown(shutdown_type what);  
416 -  
417 - /** Shut down part or all of the socket (non-throwing).  
418 -  
419 - @param what Which direction to shut down.  
420 - @param ec Set to the error code on failure.  
421   */ 426   */
422 - void shutdown(shutdown_type what, std::error_code& ec) noexcept; 427 + [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept;
423   428  
424   /** Set a socket option. 429   /** Set a socket option.
425   430  
426   Applies a type-safe socket option to the underlying socket. 431   Applies a type-safe socket option to the underlying socket.
427   The option type encodes the protocol level and option name. 432   The option type encodes the protocol level and option name.
428   433  
429   @param opt The option to set. 434   @param opt The option to set.
430   435  
431 - @throws std::logic_error if the socket is not open. 436 + @throws std::system_error `errc::bad_file_descriptor` if the
432 - @throws std::system_error on failure. 437 + socket is not open; otherwise thrown on failure.
433   */ 438   */
434   template<class Option> 439   template<class Option>
HITCBC 435   12 void set_option(Option const& opt) 440   14 void set_option(Option const& opt)
436   { 441   {
HITCBC 437   12 if (!is_open()) 442   14 if (!is_open())
HITCBC 438 - 2 detail::throw_logic_error("set_option: socket not open"); 443 + 2 detail::throw_system_error(
HITGNC   444 + 4 make_error_code(std::errc::bad_file_descriptor),
  445 + "local_stream_socket::set_option");
HITCBC 439   10 std::error_code ec = get().set_option( 446   12 std::error_code ec = get().set_option(
440   Option::level(), Option::name(), opt.data(), opt.size()); 447   Option::level(), Option::name(), opt.data(), opt.size());
HITCBC 441   10 if (ec) 448   12 if (ec)
HITGBC 442   detail::throw_system_error(ec, "local_stream_socket::set_option"); 449   2 detail::throw_system_error(ec, "local_stream_socket::set_option");
HITCBC 443   10 } 450   10 }
444   451  
445   /** Get a socket option. 452   /** Get a socket option.
446   453  
447   Retrieves the current value of a type-safe socket option. 454   Retrieves the current value of a type-safe socket option.
448   455  
449   @return The current option value. 456   @return The current option value.
450   457  
451 - @throws std::logic_error if the socket is not open. 458 + @throws std::system_error `errc::bad_file_descriptor` if the
452 - @throws std::system_error on failure. 459 + socket is not open; otherwise thrown on failure.
453   */ 460   */
454   template<class Option> 461   template<class Option>
HITCBC 455   8 Option get_option() const 462   10 Option get_option() const
456   { 463   {
HITCBC 457   8 if (!is_open()) 464   10 if (!is_open())
HITCBC 458 - 2 detail::throw_logic_error("get_option: socket not open"); 465 + 2 detail::throw_system_error(
HITGNC   466 + 4 make_error_code(std::errc::bad_file_descriptor),
  467 + "local_stream_socket::get_option");
HITCBC 459   6 Option opt{}; 468   8 Option opt{};
HITCBC 460   6 std::size_t sz = opt.size(); 469   8 std::size_t sz = opt.size();
461   std::error_code ec = 470   std::error_code ec =
HITCBC 462   6 get().get_option(Option::level(), Option::name(), opt.data(), &sz); 471   8 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
HITCBC 463   6 if (ec) 472   8 if (ec)
HITGBC 464   detail::throw_system_error(ec, "local_stream_socket::get_option"); 473   2 detail::throw_system_error(ec, "local_stream_socket::get_option");
HITCBC 465   6 opt.resize(sz); 474   6 opt.resize(sz);
HITCBC 466   6 return opt; 475   6 return opt;
467   } 476   }
468   477  
469   /** Assign an existing native socket to this object. 478   /** Assign an existing native socket to this object.
470   479  
471   Adopts a Unix domain stream socket created outside the 480   Adopts a Unix domain stream socket created outside the
472   library — from `socketpair()`, received over `SCM_RIGHTS`, 481   library — from `socketpair()`, received over `SCM_RIGHTS`,
473   or made natively — and registers it with the backend. The 482   or made natively — and registers it with the backend. The
474   socket must be a stream socket in the `AF_UNIX` family. 483   socket must be a stream socket in the `AF_UNIX` family.
475   Adoption never alters the descriptor's flags or options: on 484   Adoption never alters the descriptor's flags or options: on
476   POSIX the fd must already be non-blocking, and on Windows 485   POSIX the fd must already be non-blocking, and on Windows
477   the socket must be overlapped-capable. 486   the socket must be overlapped-capable.
478   487  
479   If this object is already open, pending operations complete 488   If this object is already open, pending operations complete
480   with `errc::operation_canceled` and the held socket is 489   with `errc::operation_canceled` and the held socket is
481   closed before the new one is adopted. 490   closed before the new one is adopted.
482   491  
483   @par Exception Safety 492   @par Exception Safety
484   Strong guarantee on validation failure: the object is 493   Strong guarantee on validation failure: the object is
485   unchanged. If backend registration fails, the object either 494   unchanged. If backend registration fails, the object either
486   retains its previous socket or is left closed, depending on 495   retains its previous socket or is left closed, depending on
487   the backend. In all failure cases the caller retains 496   the backend. In all failure cases the caller retains
488   ownership of `fd`. 497   ownership of `fd`.
489   498  
490   @param fd The native socket to adopt. On success the object 499   @param fd The native socket to adopt. On success the object
491   owns it and will close it. 500   owns it and will close it.
492   501  
493 - @throws std::system_error On validation or registration 502 + @return The error code, empty on success. Validation and
494 - failure. 503 + registration failures are normal runtime conditions when
  504 + adopting foreign descriptors.
495   */ 505   */
496 - void assign(native_handle_type fd); 506 + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
497   507  
498   /** Get the local endpoint of the socket. 508   /** Get the local endpoint of the socket.
499   509  
500   Returns the local address (path) to which the socket is bound. 510   Returns the local address (path) to which the socket is bound.
501   The endpoint is cached when the connection is established. 511   The endpoint is cached when the connection is established.
502   512  
503   @return The local endpoint, or a default endpoint if the socket 513   @return The local endpoint, or a default endpoint if the socket
504   is not connected. 514   is not connected.
505   */ 515   */
506   corosio::local_endpoint local_endpoint() const noexcept; 516   corosio::local_endpoint local_endpoint() const noexcept;
507   517  
508   /** Get the remote endpoint of the socket. 518   /** Get the remote endpoint of the socket.
509   519  
510   Returns the remote address (path) to which the socket is connected. 520   Returns the remote address (path) to which the socket is connected.
511   The endpoint is cached when the connection is established. 521   The endpoint is cached when the connection is established.
512   522  
513   @return The remote endpoint, or a default endpoint if the socket 523   @return The remote endpoint, or a default endpoint if the socket
514   is not connected. 524   is not connected.
515   */ 525   */
516   corosio::local_endpoint remote_endpoint() const noexcept; 526   corosio::local_endpoint remote_endpoint() const noexcept;
517   527  
518   protected: 528   protected:
HITCBC 519   28 local_stream_socket() noexcept = default; 529   34 local_stream_socket() noexcept = default;
520   530  
521   explicit local_stream_socket(handle h) noexcept : io_object(std::move(h)) {} 531   explicit local_stream_socket(handle h) noexcept : io_object(std::move(h)) {}
522   532  
523   private: 533   private:
524   friend class local_stream_acceptor; 534   friend class local_stream_acceptor;
525   535  
526 - void open_for_family(int family, int type, int protocol); 536 + [[nodiscard]] std::error_code
  537 + open_for_family(int family, int type, int protocol) noexcept;
527   538  
HITCBC 528   614 inline implementation& get() const noexcept 539   638 inline implementation& get() const noexcept
529   { 540   {
HITCBC 530   614 return *static_cast<implementation*>(h_.get()); 541   638 return *static_cast<implementation*>(h_.get());
531   } 542   }
532   }; 543   };
533   544  
534   } // namespace boost::corosio 545   } // namespace boost::corosio
535   546  
536   #endif // BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP 547   #endif // BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP