include/boost/corosio/connect.hpp

92.3% Lines (12/0/13) 64.3% List of functions (9/0/14)
connect.hpp
f(x) Functions (14)
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> >) :145 12x 100.0% 78.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::resolver_entry, std::allocator<boost::corosio::resolver_entry> > >(boost::corosio::tcp_socket&, std::vector<boost::corosio::resolver_entry, std::allocator<boost::corosio::resolver_entry> >) :145 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}) :184 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}) :184 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}) :184 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}) :184 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) :184 12x 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::resolver_entry, std::allocator<boost::corosio::resolver_entry> >, (anonymous namespace)::connect_condition_fragment(boost::corosio::tcp_socket&, std::vector<boost::corosio::resolver_entry, std::allocator<boost::corosio::resolver_entry> >)::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}>(boost::corosio::tcp_socket&, std::vector<boost::corosio::resolver_entry, std::allocator<boost::corosio::resolver_entry> >, (anonymous namespace)::connect_condition_fragment(boost::corosio::tcp_socket&, std::vector<boost::corosio::resolver_entry, std::allocator<boost::corosio::resolver_entry> >)::{lambda(std::error_code const&, boost::corosio::endpoint const&)#1}) :184 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::resolver_entry, std::allocator<boost::corosio::resolver_entry> >, boost::corosio::detail::default_connect_condition>(boost::corosio::tcp_socket&, std::vector<boost::corosio::resolver_entry, std::allocator<boost::corosio::resolver_entry> >, boost::corosio::detail::default_connect_condition) :184 0 0.0% 0.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> > >) :246 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> > >) :246 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) :279 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) :279 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. `resolver_results` returned from
107 `resolver::resolve`) remain alive for the coroutine's lifetime.
108 Because the range is owned by the coroutine, passing an lvalue
109 copies it; since `resolver_results` is a
110 `std::vector<resolver_entry>`, that is a deep copy of every entry.
111 Pass an rvalue (`std::move(results)`) or use the iterator overload
112 (`connect(s, results.begin(), results.end())`) to avoid the copy.
113
114 @return An awaitable completing with
115 `capy::io_result<typename Socket::endpoint_type>`:
116 - on success: default error_code and the connected endpoint;
117 - on failure of all attempts: the error from the last attempt
118 and a default-constructed endpoint;
119 - on empty range: `std::errc::no_such_device_or_address` and a
120 default-constructed endpoint.
121
122 @note The socket is closed and re-opened before each attempt, so
123 any socket options set by the caller (e.g. `no_delay`,
124 `reuse_address`) are lost. Apply options after this operation
125 completes.
126
127 If auto-opening the socket fails during an attempt, that attempt
128 completes with the open error (inherits the contract of
129 `Socket::connect`).
130
131 @par Example
132 @code
133 resolver r(ioc);
134 auto [rec, results] = co_await r.resolve("www.boost.org", "80");
135 if (rec) co_return;
136 tcp_socket s(ioc);
137 auto [cec, ep] = co_await corosio::connect(s, results);
138 @endcode
139 */
140 template<class Socket, std::ranges::input_range Range>
141 requires std::convertible_to<
142 std::ranges::range_reference_t<Range>,
143 typename Socket::endpoint_type>
144 capy::task<capy::io_result<typename Socket::endpoint_type>>
145 12x connect(Socket& s, Range endpoints)
146 {
147 return corosio::connect(
148 12x s, std::move(endpoints), detail::default_connect_condition{});
149 }
150
151 /** Asynchronously connect a socket by trying each endpoint in a range,
152 filtered by a user-supplied condition.
153
154 For each candidate the condition is invoked as
155 `cond(last_ec, ep)` where `last_ec` is the error from the most
156 recent attempt (default-constructed before the first attempt). If
157 the condition returns `false` the candidate is skipped; otherwise a
158 connect is attempted.
159
160 @param s The socket to connect. See the non-condition overload for
161 requirements.
162 @param endpoints A range of candidate endpoints, taken by value. See
163 the non-condition overload for the deep-copy caveat when passing
164 an lvalue `resolver_results`.
165 @param cond A predicate invocable with
166 `(std::error_code const&, typename Socket::endpoint_type const&)`
167 returning a value contextually convertible to `bool`.
168
169 @return Same as the non-condition overload. If every candidate is
170 rejected, completes with `std::errc::no_such_device_or_address`.
171
172 If auto-opening the socket fails, the attempt completes with the
173 open error.
174 */
175 template<class Socket, std::ranges::input_range Range, class ConnectCondition>
176 requires std::convertible_to<
177 std::ranges::range_reference_t<Range>,
178 typename Socket::endpoint_type> &&
179 std::predicate<
180 ConnectCondition&,
181 std::error_code const&,
182 typename Socket::endpoint_type const&>
183 capy::task<capy::io_result<typename Socket::endpoint_type>>
184 16x connect(Socket& s, Range endpoints, ConnectCondition cond)
185 {
186 using endpoint_type = typename Socket::endpoint_type;
187
188 std::error_code last_ec;
189
190 for (auto&& e : endpoints)
191 {
192 endpoint_type ep = e;
193
194 if (!cond(static_cast<std::error_code const&>(last_ec),
195 static_cast<endpoint_type const&>(ep)))
196 continue;
197
198 if (s.is_open())
199 s.close();
200
201 auto [ec] = co_await s.connect(ep);
202
203 if (!ec)
204 co_return {std::error_code{}, std::move(ep)};
205
206 if (ec == capy::cond::canceled)
207 co_return {ec, endpoint_type{}};
208
209 last_ec = ec;
210 }
211
212 if (!last_ec)
213 last_ec = std::make_error_code(std::errc::no_such_device_or_address);
214
215 co_return {last_ec, endpoint_type{}};
216 32x }
217
218 /** Asynchronously connect a socket by trying each endpoint in an
219 iterator range.
220
221 Behaves like the range overload, except the return value carries
222 the iterator to the successfully connected endpoint on success, or
223 `end` on failure. This mirrors Boost.Asio's iterator-based
224 `async_connect`.
225
226 @param s The socket to connect.
227 @param begin The first candidate.
228 @param end One past the last candidate.
229
230 @return An awaitable completing with `capy::io_result<Iter>`:
231 - on success: default error_code and the iterator of the
232 successful endpoint;
233 - on failure of all attempts: the error from the last attempt
234 and `end`;
235 - on empty range: `std::errc::no_such_device_or_address` and
236 `end`.
237
238 If auto-opening the socket fails, the attempt completes with the
239 open error.
240 */
241 template<class Socket, std::input_iterator Iter>
242 requires std::convertible_to<
243 std::iter_reference_t<Iter>,
244 typename Socket::endpoint_type>
245 capy::task<capy::io_result<Iter>>
246 4x connect(Socket& s, Iter begin, Iter end)
247 {
248 return corosio::connect(
249 s,
250 4x std::move(begin),
251 4x std::move(end),
252 4x detail::default_connect_condition{});
253 }
254
255 /** Asynchronously connect a socket by trying each endpoint in an
256 iterator range, filtered by a user-supplied condition.
257
258 @param s The socket to connect.
259 @param begin The first candidate.
260 @param end One past the last candidate.
261 @param cond A predicate invocable with
262 `(std::error_code const&, typename Socket::endpoint_type const&)`.
263
264 @return Same as the plain iterator overload. If every candidate is
265 rejected, completes with `std::errc::no_such_device_or_address`.
266
267 If auto-opening the socket fails, the attempt completes with the
268 open error.
269 */
270 template<class Socket, std::input_iterator Iter, class ConnectCondition>
271 requires std::convertible_to<
272 std::iter_reference_t<Iter>,
273 typename Socket::endpoint_type> &&
274 std::predicate<
275 ConnectCondition&,
276 std::error_code const&,
277 typename Socket::endpoint_type const&>
278 capy::task<capy::io_result<Iter>>
279 4x connect(Socket& s, Iter begin, Iter end, ConnectCondition cond)
280 {
281 using endpoint_type = typename Socket::endpoint_type;
282
283 std::error_code last_ec;
284
285 for (Iter it = begin; it != end; ++it)
286 {
287 endpoint_type ep = *it;
288
289 if (!cond(static_cast<std::error_code const&>(last_ec),
290 static_cast<endpoint_type const&>(ep)))
291 continue;
292
293 if (s.is_open())
294 s.close();
295
296 auto [ec] = co_await s.connect(ep);
297
298 if (!ec)
299 co_return {std::error_code{}, std::move(it)};
300
301 if (ec == capy::cond::canceled)
302 co_return {ec, std::move(end)};
303
304 last_ec = ec;
305 }
306
307 if (!last_ec)
308 last_ec = std::make_error_code(std::errc::no_such_device_or_address);
309
310 co_return {last_ec, std::move(end)};
311 8x }
312
313 } // namespace boost::corosio
314
315 #endif
316