include/boost/corosio/local_stream_socket.hpp

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