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