include/boost/corosio/local_stream_socket.hpp

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