include/boost/corosio/local_stream_socket.hpp

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