82.93% Lines (34/41) 100.00% Functions (3/3)
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/task.hpp> 19   #include <boost/capy/task.hpp>
20   20  
21   #include <cstdio> 21   #include <cstdio>
22   #include <stdexcept> 22   #include <stdexcept>
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.
35   35  
36   @tparam Socket The socket type (default `tcp_socket`). 36   @tparam Socket The socket type (default `tcp_socket`).
37   @tparam Acceptor The acceptor type (default `tcp_acceptor`). 37   @tparam Acceptor The acceptor type (default `tcp_acceptor`).
38   @tparam Linger Whether to enable `SO_LINGER` with a zero timeout 38   @tparam Linger Whether to enable `SO_LINGER` with a zero timeout
39   on both sockets (default `true`). 39   on both sockets (default `true`).
40   40  
41   @param ctx The I/O context for the sockets. 41   @param ctx The I/O context for the sockets.
42   42  
43   @return A pair of connected sockets. 43   @return A pair of connected sockets.
44   44  
45   @throws std::runtime_error if opening, binding, listening, 45   @throws std::runtime_error if opening, binding, listening,
46   accepting, or connecting fails. 46   accepting, or connecting fails.
47   */ 47   */
48   template< 48   template<
49   class Socket = tcp_socket, 49   class Socket = tcp_socket,
50   class Acceptor = tcp_acceptor, 50   class Acceptor = tcp_acceptor,
51   bool Linger = true> 51   bool Linger = true>
52   std::pair<Socket, Socket> 52   std::pair<Socket, Socket>
HITCBC 53   913 make_socket_pair(io_context& ctx) 53   913 make_socket_pair(io_context& ctx)
54   { 54   {
HITCBC 55   913 auto ex = ctx.get_executor(); 55   913 auto ex = ctx.get_executor();
56   56  
HITCBC 57   913 std::error_code accept_ec; 57   913 std::error_code accept_ec;
HITCBC 58   913 std::error_code connect_ec; 58   913 std::error_code connect_ec;
HITCBC 59   913 bool accept_done = false; 59   913 bool accept_done = false;
HITCBC 60   913 bool connect_done = false; 60   913 bool connect_done = false;
61   61  
HITCBC 62   913 Acceptor acc(ctx); 62   913 Acceptor acc(ctx);
HITCBC 63   913 if (auto open_ec = acc.open()) 63   913 if (auto open_ec = acc.open())
MISUBC 64   ✗ throw std::runtime_error( 64   ✗ throw std::runtime_error(
65   "socket_pair open failed: " + open_ec.message()); 65   "socket_pair open failed: " + open_ec.message());
HITCBC 66   913 acc.set_option(socket_option::reuse_address(true)); 66   913 acc.set_option(socket_option::reuse_address(true));
HITCBC 67   913 if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) 67   913 if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
MISUBC 68   ✗ throw std::runtime_error("socket_pair bind failed: " + ec.message()); 68   ✗ throw std::runtime_error("socket_pair bind failed: " + ec.message());
HITCBC 69   913 if (auto ec = acc.listen()) 69   913 if (auto ec = acc.listen())
MISUBC 70   ✗ throw std::runtime_error("socket_pair listen failed: " + ec.message()); 70   ✗ throw std::runtime_error("socket_pair listen failed: " + ec.message());
HITCBC 71   913 auto port = acc.local_endpoint().port(); 71   913 auto port = acc.local_endpoint().port();
72   72  
HITCBC 73   913 Socket s1(ctx); 73   913 Socket s1(ctx);
HITCBC 74   913 Socket s2(ctx); 74   913 Socket s2(ctx);
HITCBC 75   913 if (auto open_ec = s2.open()) 75   913 if (auto open_ec = s2.open())
MISUBC 76   ✗ throw std::runtime_error( 76   ✗ throw std::runtime_error(
77   "socket_pair open failed: " + open_ec.message()); 77   "socket_pair open failed: " + open_ec.message());
78   78  
HITCBC 79   913 capy::run_async(ex)( 79   913 capy::run_async(ex)(
HITCBC 80   1826 [](Acceptor& a, Socket& s, std::error_code& ec_out, 80   1826 [](Acceptor& a, Socket& s, std::error_code& ec_out,
81   bool& done_out) -> capy::task<> { 81   bool& done_out) -> capy::task<> {
82   auto [ec] = co_await a.accept(s); 82   auto [ec] = co_await a.accept(s);
83   ec_out = ec; 83   ec_out = ec;
84   done_out = true; 84   done_out = true;
85   }(acc, s1, accept_ec, accept_done)); 85   }(acc, s1, accept_ec, accept_done));
86   86  
87   // A failed connect leaves nothing for the accept to receive. 87   // A failed connect leaves nothing for the accept to receive.
HITCBC 88   1826 capy::run_async(ex)( 88   1826 capy::run_async(ex)(
HITCBC 89   913 [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out, 89   913 [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out,
90   bool& done_out) -> capy::task<> { 90   bool& done_out) -> capy::task<> {
91   auto [ec] = co_await s.connect(ep); 91   auto [ec] = co_await s.connect(ep);
92   ec_out = ec; 92   ec_out = ec;
93   done_out = true; 93   done_out = true;
94   if (ec) 94   if (ec)
95   a.cancel(); 95   a.cancel();
HITCBC 96   1826 }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec, 96   1826 }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec,
97   connect_done)); 97   connect_done));
98   98  
99   // Both coroutines write to locals here, so they must finish before 99   // Both coroutines write to locals here, so they must finish before
100   // this returns or throws, even on a context something stopped. 100   // this returns or throws, even on a context something stopped.
HITCBC 101   1827 while (!accept_done || !connect_done) 101   1827 while (!accept_done || !connect_done)
102   { 102   {
HITCBC 103   914 ctx.run(); 103   914 ctx.run();
HITCBC 104   914 ctx.restart(); 104   914 ctx.restart();
105   } 105   }
106   106  
HITCBC 107   913 if (connect_ec) 107   913 if (connect_ec)
108   { 108   {
HITCBC 109   5 std::fprintf( 109   5 std::fprintf(
110   stderr, "socket_pair: connect failed: %s\n", 110   stderr, "socket_pair: connect failed: %s\n",
111   connect_ec.message().c_str()); 111   connect_ec.message().c_str());
HITCBC 112   5 acc.close(); 112   5 acc.close();
HITCBC 113   5 s1.close(); 113   5 s1.close();
HITCBC 114   5 throw std::runtime_error("socket_pair connect failed"); 114   5 throw std::runtime_error("socket_pair connect failed");
115   } 115   }
116   116  
HITCBC 117   908 if (accept_ec) 117   908 if (accept_ec)
118   { 118   {
MISUBC 119   ✗ std::fprintf( 119   ✗ std::fprintf(
120   stderr, "socket_pair: accept failed: %s\n", 120   stderr, "socket_pair: accept failed: %s\n",
121   accept_ec.message().c_str()); 121   accept_ec.message().c_str());
MISUBC 122   ✗ acc.close(); 122   ✗ acc.close();
MISUBC 123   ✗ throw std::runtime_error("socket_pair accept failed"); 123   ✗ throw std::runtime_error("socket_pair accept failed");
124   } 124   }
125   125  
HITCBC 126   908 acc.close(); 126   908 acc.close();
127   127  
128   if constexpr (Linger) 128   if constexpr (Linger)
129   { 129   {
HITCBC 130   118 s1.set_option(socket_option::linger(true, 0)); 130   118 s1.set_option(socket_option::linger(true, 0));
HITCBC 131   118 s2.set_option(socket_option::linger(true, 0)); 131   118 s2.set_option(socket_option::linger(true, 0));
132   } 132   }
133   133  
HITCBC 134   1816 return {std::move(s1), std::move(s2)}; 134   1816 return {std::move(s1), std::move(s2)};
HITCBC 135   923 } 135   923 }
136   136  
137   } // namespace boost::corosio::test 137   } // namespace boost::corosio::test
138   138  
139   #endif 139   #endif