include/boost/corosio/connect.hpp

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