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