include/boost/corosio/local_stream_socket.hpp

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