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