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