87.23% Lines (41/47)
100.00% Functions (4/4)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | // Copyright (c) 2026 Steve Gerbino | 3 | // Copyright (c) 2026 Steve Gerbino | |||||
| 4 | // | 4 | // | |||||
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||||
| 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 7 | // | 7 | // | |||||
| 8 | // Official repository: https://github.com/cppalliance/corosio | 8 | // Official repository: https://github.com/cppalliance/corosio | |||||
| 9 | // | 9 | // | |||||
| 10 | 10 | |||||||
| 11 | #ifndef BOOST_COROSIO_TEST_SOCKET_PAIR_HPP | 11 | #ifndef BOOST_COROSIO_TEST_SOCKET_PAIR_HPP | |||||
| 12 | #define BOOST_COROSIO_TEST_SOCKET_PAIR_HPP | 12 | #define BOOST_COROSIO_TEST_SOCKET_PAIR_HPP | |||||
| 13 | 13 | |||||||
| 14 | #include <boost/corosio/io_context.hpp> | 14 | #include <boost/corosio/io_context.hpp> | |||||
| 15 | #include <boost/corosio/tcp_acceptor.hpp> | 15 | #include <boost/corosio/tcp_acceptor.hpp> | |||||
| 16 | #include <boost/corosio/tcp_socket.hpp> | 16 | #include <boost/corosio/tcp_socket.hpp> | |||||
| 17 | #include <boost/corosio/socket_option.hpp> | 17 | #include <boost/corosio/socket_option.hpp> | |||||
| 18 | #include <boost/capy/ex/run_async.hpp> | 18 | #include <boost/capy/ex/run_async.hpp> | |||||
| 19 | + | #include <boost/capy/results.hpp> | ||||||
| 19 | #include <boost/capy/task.hpp> | 20 | #include <boost/capy/task.hpp> | |||||
| 20 | 21 | |||||||
| 21 | - | #include <stdexcept> | ||||||
| 22 | #include <cstdio> | 22 | #include <cstdio> | |||||
| 23 | #include <system_error> | 23 | #include <system_error> | |||||
| 24 | #include <utility> | 24 | #include <utility> | |||||
| 25 | 25 | |||||||
| 26 | namespace boost::corosio::test { | 26 | namespace boost::corosio::test { | |||||
| 27 | 27 | |||||||
| 28 | /** Create a connected pair of sockets. | 28 | /** Create a connected pair of sockets. | |||||
| 29 | 29 | |||||||
| 30 | Creates two sockets connected via loopback TCP sockets. | 30 | Creates two sockets connected via loopback TCP sockets. | |||||
| 31 | Data written to one socket can be read from the other. | 31 | Data written to one socket can be read from the other. | |||||
| 32 | 32 | |||||||
| 33 | Runs `ctx` until the connection is made, restarting it first if | 33 | Runs `ctx` until the connection is made, restarting it first if | |||||
| 34 | - | it was stopped, and leaves it restarted. | 34 | + | it was stopped, and leaves it restarted. A failure before the | |||
| 35 | + | connection attempt returns without running `ctx`, so its state is | ||||||
| 36 | + | unchanged. | ||||||
| 37 | + | |||||||
| 38 | + | On failure the returned sockets are closed and the error code | ||||||
| 39 | + | is that of the step that failed. | ||||||
| 35 | 40 | |||||||
| 36 | @tparam Socket The socket type (default `tcp_socket`). | 41 | @tparam Socket The socket type (default `tcp_socket`). | |||||
| 37 | @tparam Acceptor The acceptor type (default `tcp_acceptor`). | 42 | @tparam Acceptor The acceptor type (default `tcp_acceptor`). | |||||
| 38 | @tparam Linger Whether to enable `SO_LINGER` with a zero timeout | 43 | @tparam Linger Whether to enable `SO_LINGER` with a zero timeout | |||||
| 39 | on both sockets (default `true`). | 44 | on both sockets (default `true`). | |||||
| 40 | 45 | |||||||
| 41 | @param ctx The I/O context for the sockets. | 46 | @param ctx The I/O context for the sockets. | |||||
| 42 | 47 | |||||||
| 43 | - | @return A pair of connected sockets. | 48 | + | @return The error code and the connected sockets. | |||
| 44 | - | |||||||
| 45 | - | @throws std::runtime_error if opening, binding, listening, | ||||||
| 46 | - | accepting, or connecting fails. | ||||||
| 47 | */ | 49 | */ | |||||
| 48 | template< | 50 | template< | |||||
| 49 | class Socket = tcp_socket, | 51 | class Socket = tcp_socket, | |||||
| 50 | class Acceptor = tcp_acceptor, | 52 | class Acceptor = tcp_acceptor, | |||||
| 51 | bool Linger = true> | 53 | bool Linger = true> | |||||
| 52 | - | std::pair<Socket, Socket> | 54 | + | [[nodiscard]] capy::results<Socket, Socket> | |||
| HITCBC | 53 | 917 | make_socket_pair(io_context& ctx) | 55 | 942 | make_socket_pair(io_context& ctx) | ||
| 54 | { | 56 | { | |||||
| HITCBC | 55 | 917 | auto ex = ctx.get_executor(); | 57 | 942 | auto ex = ctx.get_executor(); | ||
| 56 | 58 | |||||||
| HITGNC | 59 | + | 942 | Socket s1(ctx); | ||||
| HITGNC | 60 | + | 942 | Socket s2(ctx); | ||||
| 61 | + | |||||||
| HITGNC | 62 | + | 967 | auto fail = [&](std::error_code ec) -> capy::results<Socket, Socket> { | ||||
| HITGNC | 63 | + | 25 | s1.close(); | ||||
| HITGNC | 64 | + | 25 | s2.close(); | ||||
| HITGNC | 65 | + | 25 | return {ec, std::move(s1), std::move(s2)}; | ||||
| 66 | + | }; | ||||||
| 67 | + | |||||||
| HITCBC | 57 | 917 | std::error_code accept_ec; | 68 | 942 | std::error_code accept_ec; | ||
| HITCBC | 58 | 917 | std::error_code connect_ec; | 69 | 942 | std::error_code connect_ec; | ||
| HITCBC | 59 | 917 | bool accept_done = false; | 70 | 942 | bool accept_done = false; | ||
| HITCBC | 60 | 917 | bool connect_done = false; | 71 | 942 | bool connect_done = false; | ||
| 61 | 72 | |||||||
| HITCBC | 62 | 917 | Acceptor acc(ctx); | 73 | 942 | Acceptor acc(ctx); | ||
| HITCBC | 63 | - | 917 | if (auto open_ec = acc.open()) | 74 | + | 942 | if (auto ec = acc.open()) |
| HITGBC | 64 | - | ✗ | throw std::runtime_error( | 75 | + | 5 | return fail(ec); |
| HITGIC | 65 | - | "socket_pair open failed: " + open_ec.message()); | 76 | + | 937 | if (auto ec = acc.set_option(socket_option::reuse_address(true))) | |
| HITCBC | 66 | - | 917 | acc.set_option(socket_option::reuse_address(true)); | 77 | + | 5 | return fail(ec); |
| HITCBC | 67 | 917 | if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) | 78 | 932 | if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) | ||
| MISUBC | 68 | - | ✗ | throw std::runtime_error("socket_pair bind failed: " + ec.message()); | 79 | + | ✗ | return fail(ec); |
| HITCBC | 69 | 917 | if (auto ec = acc.listen()) | 80 | 932 | if (auto ec = acc.listen()) | ||
| MISUBC | 70 | - | ✗ | throw std::runtime_error("socket_pair listen failed: " + ec.message()); | 81 | + | ✗ | return fail(ec); |
| HITCBC | 71 | 917 | auto port = acc.local_endpoint().port(); | 82 | 932 | auto port = acc.local_endpoint().port(); | ||
| 72 | 83 | |||||||
| HITCBC | 73 | - | 917 | Socket s1(ctx); | 84 | + | 932 | if (auto ec = s2.open()) |
| MISLBC | 74 | - | 917 | Socket s2(ctx); | 85 | + | ✗ | return fail(ec); |
| DCB | 75 | - | 917 | if (auto open_ec = s2.open()) | ||||
| DUB | 76 | - | ✗ | throw std::runtime_error( | ||||
| 77 | - | "socket_pair open failed: " + open_ec.message()); | ||||||
| 78 | 86 | |||||||
| HITCBC | 79 | 917 | capy::run_async(ex)( | 87 | 932 | capy::run_async(ex)( | ||
| HITCBC | 80 | 1834 | [](Acceptor& a, Socket& s, std::error_code& ec_out, | 88 | 1864 | [](Acceptor& a, Socket& s, std::error_code& ec_out, | ||
| 81 | bool& done_out) -> capy::task<> { | 89 | bool& done_out) -> capy::task<> { | |||||
| 82 | auto [ec] = co_await a.accept(s); | 90 | auto [ec] = co_await a.accept(s); | |||||
| 83 | ec_out = ec; | 91 | ec_out = ec; | |||||
| 84 | done_out = true; | 92 | done_out = true; | |||||
| 85 | }(acc, s1, accept_ec, accept_done)); | 93 | }(acc, s1, accept_ec, accept_done)); | |||||
| 86 | 94 | |||||||
| 87 | // A failed connect leaves nothing for the accept to receive. | 95 | // A failed connect leaves nothing for the accept to receive. | |||||
| HITCBC | 88 | 1834 | capy::run_async(ex)( | 96 | 1864 | capy::run_async(ex)( | ||
| HITCBC | 89 | 917 | [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out, | 97 | 932 | [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out, | ||
| 90 | bool& done_out) -> capy::task<> { | 98 | bool& done_out) -> capy::task<> { | |||||
| 91 | auto [ec] = co_await s.connect(ep); | 99 | auto [ec] = co_await s.connect(ep); | |||||
| 92 | ec_out = ec; | 100 | ec_out = ec; | |||||
| 93 | done_out = true; | 101 | done_out = true; | |||||
| 94 | if (ec) | 102 | if (ec) | |||||
| 95 | a.cancel(); | 103 | a.cancel(); | |||||
| HITCBC | 96 | 1834 | }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec, | 104 | 1864 | }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec, | ||
| 97 | connect_done)); | 105 | connect_done)); | |||||
| 98 | 106 | |||||||
| 99 | // Both coroutines write to locals here, so they must finish before | 107 | // Both coroutines write to locals here, so they must finish before | |||||
| 100 | - | // this returns or throws, even on a context something stopped. | 108 | + | // this returns, even on a context something stopped. | |||
| HITCBC | 101 | 1835 | while (!accept_done || !connect_done) | 109 | 1865 | while (!accept_done || !connect_done) | ||
| 102 | { | 110 | { | |||||
| HITCBC | 103 | 918 | ctx.run(); | 111 | 933 | ctx.run(); | ||
| HITCBC | 104 | 918 | ctx.restart(); | 112 | 933 | ctx.restart(); | ||
| 105 | } | 113 | } | |||||
| 106 | 114 | |||||||
| HITCBC | 107 | 917 | if (connect_ec) | 115 | 932 | if (connect_ec) | ||
| 108 | { | 116 | { | |||||
| HITCBC | 109 | 5 | std::fprintf( | 117 | 5 | std::fprintf( | ||
| 110 | stderr, "socket_pair: connect failed: %s\n", | 118 | stderr, "socket_pair: connect failed: %s\n", | |||||
| 111 | connect_ec.message().c_str()); | 119 | connect_ec.message().c_str()); | |||||
| HITCBC | 112 | 5 | acc.close(); | 120 | 5 | acc.close(); | ||
| HITCBC | 113 | - | 5 | s1.close(); | 121 | + | 5 | return fail(connect_ec); |
| DCB | 114 | - | 5 | throw std::runtime_error("socket_pair connect failed"); | ||||
| 115 | } | 122 | } | |||||
| 116 | 123 | |||||||
| HITCBC | 117 | 912 | if (accept_ec) | 124 | 927 | if (accept_ec) | ||
| 118 | { | 125 | { | |||||
| MISUBC | 119 | ✗ | std::fprintf( | 126 | ✗ | std::fprintf( | ||
| 120 | stderr, "socket_pair: accept failed: %s\n", | 127 | stderr, "socket_pair: accept failed: %s\n", | |||||
| 121 | accept_ec.message().c_str()); | 128 | accept_ec.message().c_str()); | |||||
| MISUBC | 122 | ✗ | acc.close(); | 129 | ✗ | acc.close(); | ||
| MISUBC | 123 | - | ✗ | throw std::runtime_error("socket_pair accept failed"); | 130 | + | ✗ | return fail(accept_ec); |
| 124 | } | 131 | } | |||||
| 125 | 132 | |||||||
| HITCBC | 126 | 912 | acc.close(); | 133 | 927 | acc.close(); | ||
| 127 | 134 | |||||||
| 128 | if constexpr (Linger) | 135 | if constexpr (Linger) | |||||
| 129 | { | 136 | { | |||||
| HITCBC | 130 | - | 122 | s1.set_option(socket_option::linger(true, 0)); | 137 | + | 137 | if (auto ec = s1.set_option(socket_option::linger(true, 0))) |
| HITCBC | 131 | - | 122 | s2.set_option(socket_option::linger(true, 0)); | 138 | + | 5 | return fail(ec); |
| HITGNC | 139 | + | 132 | if (auto ec = s2.set_option(socket_option::linger(true, 0))) | ||||
| HITGNC | 140 | + | 5 | return fail(ec); | ||||
| 132 | } | 141 | } | |||||
| 133 | 142 | |||||||
| HITCBC | 134 | - | 1824 | return {std::move(s1), std::move(s2)}; | 143 | + | 917 | return {std::error_code{}, std::move(s1), std::move(s2)}; |
| HITCBC | 135 | 927 | } | 144 | 942 | } | ||
| 136 | 145 | |||||||
| 137 | } // namespace boost::corosio::test | 146 | } // namespace boost::corosio::test | |||||
| 138 | 147 | |||||||
| 139 | #endif | 148 | #endif | |||||