include/boost/corosio/connect.hpp

100.0% Lines (11 / 11) 75.0% Functions (9 / 12)
connect.hpp
f(x) Functions (12)
Function Calls Lines Blocks
bool boost::corosio::detail::default_connect_condition::operator()<boost::corosio::endpoint>(std::error_code const&, boost::corosio::endpoint const&) const :54 20x 100.0% 100.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >) :134 12x 100.0% 70.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, (anonymous namespace)::connect_condition_fragment(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >)::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, (anonymous namespace)::connect_condition_fragment(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >)::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :171 0 0.0% 0.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::epoll_t{}>::testCondSelectiveSkip()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::epoll_t{}>::testCondSelectiveSkip()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :171 1x 100.0% 42.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::epoll_t{}>::testCondSkipsAll()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::epoll_t{}>::testCondSkipsAll()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :171 1x 100.0% 42.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::select_t{}>::testCondSelectiveSkip()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::select_t{}>::testCondSelectiveSkip()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :171 1x 100.0% 42.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::select_t{}>::testCondSkipsAll()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::connect_test<boost::corosio::select_t{}>::testCondSkipsAll()::{lambda()#1}::operator()() const::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :171 1x 100.0% 42.0% boost::capy::task<std::tuple<std::error_code, boost::corosio::tcp_socket::endpoint_type> > boost::corosio::connect<boost::corosio::tcp_socket, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::detail::default_connect_condition>(boost::corosio::tcp_socket&, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >, boost::corosio::detail::default_connect_condition) :171 12x 100.0% 42.0% boost::capy::task<std::tuple<std::error_code, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > > > boost::corosio::connect<boost::corosio::tcp_socket, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > >(boost::corosio::tcp_socket&, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >) :234 0 0.0% 0.0% boost::capy::task<std::tuple<std::error_code, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > > > boost::corosio::connect<boost::corosio::tcp_socket, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > >(boost::corosio::tcp_socket&, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >) :234 4x 100.0% 100.0% boost::capy::task<std::tuple<std::error_code, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > > > boost::corosio::connect<boost::corosio::tcp_socket, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, boost::corosio::detail::default_connect_condition>(boost::corosio::tcp_socket&, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, __gnu_cxx::__normal_iterator<boost::corosio::endpoint const*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, boost::corosio::detail::default_connect_condition) :265 0 0.0% 0.0% boost::capy::task<std::tuple<std::error_code, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > > > > boost::corosio::connect<boost::corosio::tcp_socket, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, boost::corosio::detail::default_connect_condition>(boost::corosio::tcp_socket&, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, __gnu_cxx::__normal_iterator<boost::corosio::endpoint*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> > >, boost::corosio::detail::default_connect_condition) :265 4x 100.0% 44.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_CONNECT_HPP
11 #define BOOST_COROSIO_CONNECT_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14
15 #include <boost/capy/cond.hpp>
16 #include <boost/capy/io_result.hpp>
17 #include <boost/capy/task.hpp>
18
19 #include <concepts>
20 #include <iterator>
21 #include <ranges>
22 #include <system_error>
23 #include <utility>
24
25 /*
26 Range-based composed connect operation.
27
28 These free functions try each endpoint in a range (or iterator pair)
29 in order, returning on the first successful connect. Between attempts
30 the socket is closed so that the next attempt can auto-open with the
31 correct address family (e.g. going from IPv4 to IPv6 candidates).
32
33 The iteration semantics follow Boost.Asio's range/iterator async_connect:
34 on success, the successful endpoint (or its iterator) is returned; on
35 all-fail, the last attempt's error code is returned; on an empty range
36 (or when a connect_condition rejects every candidate),
37 std::errc::no_such_device_or_address is returned, matching the error
38 the resolver uses for "no results" in posix_resolver_service.
39
40 The operation is a plain coroutine; cancellation is propagated to the
41 inner per-endpoint connect via the affine awaitable protocol on io_env.
42 */
43
44 namespace boost::corosio {
45
46 namespace detail {
47
48 /* Always-true connect condition used by the overloads that take no
49 user-supplied predicate. Kept at namespace-detail scope so it has a
50 stable linkage name across translation units. */
51 struct default_connect_condition
52 {
53 template<class Endpoint>
54 20x bool operator()(std::error_code const&, Endpoint const&) const noexcept
55 {
56 20x return true;
57 }
58 };
59
60 } // namespace detail
61
62 /* Forward declarations so the non-condition overloads can delegate
63 to the condition overloads via qualified lookup (qualified calls
64 bind to the overload set visible at definition, not instantiation). */
65
66 template<class Socket, std::ranges::input_range Range, class ConnectCondition>
67 requires std::convertible_to<
68 std::ranges::range_reference_t<Range>,
69 typename Socket::endpoint_type> &&
70 std::predicate<
71 ConnectCondition&,
72 std::error_code const&,
73 typename Socket::endpoint_type const&>
74 capy::task<capy::io_result<typename Socket::endpoint_type>>
75 connect(Socket& s, Range endpoints, ConnectCondition cond);
76
77 template<class Socket, std::input_iterator Iter, class ConnectCondition>
78 requires std::convertible_to<
79 std::iter_reference_t<Iter>,
80 typename Socket::endpoint_type> &&
81 std::predicate<
82 ConnectCondition&,
83 std::error_code const&,
84 typename Socket::endpoint_type const&>
85 capy::task<capy::io_result<Iter>>
86 connect(Socket& s, Iter begin, Iter end, ConnectCondition cond);
87
88 /** Asynchronously connect a socket by trying each endpoint in a range.
89
90 Each candidate is tried in order. Before each attempt the socket is
91 closed (so the next `connect` auto-opens with the candidate's
92 address family). On first successful connect, the operation
93 completes with the connected endpoint.
94
95 @par Cancellation
96 Supports cancellation via the affine awaitable protocol. If a
97 per-endpoint connect completes with `capy::cond::canceled` the
98 operation completes immediately with that error and does not try
99 further endpoints.
100
101 @param s The socket to connect. Must have a `connect(endpoint)`
102 member returning an awaitable, plus `close()` and `is_open()`.
103 If the socket is already open, it will be closed before the
104 first attempt.
105 @param endpoints A range of candidate endpoints. Taken by value
106 so temporaries (e.g. the `std::vector<endpoint>` returned from
107 `resolver::resolve`) remain alive for the coroutine's lifetime.
108
109 @return An awaitable completing with
110 `capy::io_result<typename Socket::endpoint_type>`:
111 - on success: default error_code and the connected endpoint;
112 - on failure of all attempts: the error from the last attempt
113 and a default-constructed endpoint;
114 - on empty range: `std::errc::no_such_device_or_address` and a
115 default-constructed endpoint.
116
117 @note The socket is closed and re-opened before each attempt, so
118 any socket options set by the caller (e.g. `no_delay`,
119 `reuse_address`) are lost. Apply options after this operation
120 completes.
121
122 If auto-opening the socket fails during an attempt, that attempt
123 completes with the open error (inherits the contract of
124 `Socket::connect`).
125
126 @par Example
127 @par !example connect
128 */
129 template<class Socket, std::ranges::input_range Range>
130 requires std::convertible_to<
131 std::ranges::range_reference_t<Range>,
132 typename Socket::endpoint_type>
133 capy::task<capy::io_result<typename Socket::endpoint_type>>
134 12x connect(Socket& s, Range endpoints)
135 {
136 detail::default_connect_condition cond;
137 12x return corosio::connect(s, std::move(endpoints), cond);
138 }
139
140 /** Asynchronously connect a socket by trying each endpoint in a range,
141 filtered by a user-supplied condition.
142
143 For each candidate the condition is invoked as
144 `cond(last_ec, ep)` where `last_ec` is the error from the most
145 recent attempt (default-constructed before the first attempt). If
146 the condition returns `false` the candidate is skipped; otherwise a
147 connect is attempted.
148
149 @param s The socket to connect. See the non-condition overload for
150 requirements.
151 @param endpoints A range of candidate endpoints, taken by value.
152 @param cond A predicate invocable with
153 `(std::error_code const&, typename Socket::endpoint_type const&)`
154 returning a value contextually convertible to `bool`.
155
156 @return Same as the non-condition overload. If every candidate is
157 rejected, completes with `std::errc::no_such_device_or_address`.
158
159 If auto-opening the socket fails, the attempt completes with the
160 open error.
161 */
162 template<class Socket, std::ranges::input_range Range, class ConnectCondition>
163 requires std::convertible_to<
164 std::ranges::range_reference_t<Range>,
165 typename Socket::endpoint_type> &&
166 std::predicate<
167 ConnectCondition&,
168 std::error_code const&,
169 typename Socket::endpoint_type const&>
170 capy::task<capy::io_result<typename Socket::endpoint_type>>
171 16x connect(Socket& s, Range endpoints, ConnectCondition cond)
172 {
173 using endpoint_type = typename Socket::endpoint_type;
174
175 std::error_code last_ec;
176
177 for (auto&& e : endpoints)
178 {
179 endpoint_type ep = e;
180
181 if (!cond(
182 static_cast<std::error_code const&>(last_ec),
183 static_cast<endpoint_type const&>(ep)))
184 continue;
185
186 if (s.is_open())
187 s.close();
188
189 auto [ec] = co_await s.connect(ep);
190
191 if (!ec)
192 co_return {std::error_code{}, std::move(ep)};
193
194 if (ec == capy::cond::canceled)
195 co_return {ec, endpoint_type{}};
196
197 last_ec = ec;
198 }
199
200 if (!last_ec)
201 last_ec = std::make_error_code(std::errc::no_such_device_or_address);
202
203 co_return {last_ec, endpoint_type{}};
204 32x }
205
206 /** Asynchronously connect a socket by trying each endpoint in an
207 iterator range.
208
209 Behaves like the range overload, except the return value carries
210 the iterator to the successfully connected endpoint on success, or
211 `end` on failure. This mirrors Boost.Asio's iterator-based
212 `async_connect`.
213
214 @param s The socket to connect.
215 @param begin The first candidate.
216 @param end One past the last candidate.
217
218 @return An awaitable completing with `capy::io_result<Iter>`:
219 - on success: default error_code and the iterator of the
220 successful endpoint;
221 - on failure of all attempts: the error from the last attempt
222 and `end`;
223 - on empty range: `std::errc::no_such_device_or_address` and
224 `end`.
225
226 If auto-opening the socket fails, the attempt completes with the
227 open error.
228 */
229 template<class Socket, std::input_iterator Iter>
230 requires std::convertible_to<
231 std::iter_reference_t<Iter>,
232 typename Socket::endpoint_type>
233 capy::task<capy::io_result<Iter>>
234 4x connect(Socket& s, Iter begin, Iter end)
235 {
236 return corosio::connect(
237 4x s, std::move(begin), std::move(end),
238 4x detail::default_connect_condition{});
239 }
240
241 /** Asynchronously connect a socket by trying each endpoint in an
242 iterator range, filtered by a user-supplied condition.
243
244 @param s The socket to connect.
245 @param begin The first candidate.
246 @param end One past the last candidate.
247 @param cond A predicate invocable with
248 `(std::error_code const&, typename Socket::endpoint_type const&)`.
249
250 @return Same as the plain iterator overload. If every candidate is
251 rejected, completes with `std::errc::no_such_device_or_address`.
252
253 If auto-opening the socket fails, the attempt completes with the
254 open error.
255 */
256 template<class Socket, std::input_iterator Iter, class ConnectCondition>
257 requires std::convertible_to<
258 std::iter_reference_t<Iter>,
259 typename Socket::endpoint_type> &&
260 std::predicate<
261 ConnectCondition&,
262 std::error_code const&,
263 typename Socket::endpoint_type const&>
264 capy::task<capy::io_result<Iter>>
265 4x connect(Socket& s, Iter begin, Iter end, ConnectCondition cond)
266 {
267 using endpoint_type = typename Socket::endpoint_type;
268
269 std::error_code last_ec;
270
271 for (Iter it = begin; it != end; ++it)
272 {
273 endpoint_type ep = *it;
274
275 if (!cond(
276 static_cast<std::error_code const&>(last_ec),
277 static_cast<endpoint_type const&>(ep)))
278 continue;
279
280 if (s.is_open())
281 s.close();
282
283 auto [ec] = co_await s.connect(ep);
284
285 if (!ec)
286 co_return {std::error_code{}, std::move(it)};
287
288 if (ec == capy::cond::canceled)
289 co_return {ec, std::move(end)};
290
291 last_ec = ec;
292 }
293
294 if (!last_ec)
295 last_ec = std::make_error_code(std::errc::no_such_device_or_address);
296
297 co_return {last_ec, std::move(end)};
298 8x }
299
300 } // namespace boost::corosio
301
302 #endif
303