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