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