LCOV - code coverage report
Current view: top level - corosio/test - socket_pair.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 87.2 % 47 41 6
Test Date: 2026-10-28 12:45:41 Functions: 83.3 % 24 20 4

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

Generated by: LCOV version 2.3