wreq-rb 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +257 -102
  3. data/ext/wreq_rb/Cargo.toml +4 -4
  4. data/ext/wreq_rb/src/client.rs +1 -1
  5. data/lib/wreq-rb/version.rb +1 -1
  6. data/patches/0001-add-transfer-size-tracking.patch +11 -15
  7. data/vendor/wreq/Cargo.toml +9 -8
  8. data/vendor/wreq/README.md +5 -5
  9. data/vendor/wreq/bench/support/bench.rs +6 -2
  10. data/vendor/wreq/bench/support/client.rs +88 -1
  11. data/vendor/wreq/bench/support/exec.rs +0 -0
  12. data/vendor/wreq/bench/support/rt.rs +34 -0
  13. data/vendor/wreq/bench/support/server.rs +1 -1
  14. data/vendor/wreq/bench/support.rs +1 -15
  15. data/vendor/wreq/examples/cert_store.rs +13 -13
  16. data/vendor/wreq/examples/request_with_emulate.rs +1 -1
  17. data/vendor/wreq/examples/tcp_linger.rs +22 -0
  18. data/vendor/wreq/src/client/layer/client/pool.rs +17 -17
  19. data/vendor/wreq/src/client/layer/client.rs +2 -0
  20. data/vendor/wreq/src/client/layer/decoder.rs +71 -17
  21. data/vendor/wreq/src/client/layer/redirect/future.rs +49 -63
  22. data/vendor/wreq/src/client/layer/redirect/policy.rs +2 -26
  23. data/vendor/wreq/src/client/layer/redirect.rs +48 -60
  24. data/vendor/wreq/src/client/layer/retry.rs +12 -15
  25. data/vendor/wreq/src/client/layer/timeout/body.rs +27 -21
  26. data/vendor/wreq/src/client/layer/timeout/future.rs +33 -58
  27. data/vendor/wreq/src/client/layer/timeout.rs +8 -14
  28. data/vendor/wreq/src/client/request.rs +4 -0
  29. data/vendor/wreq/src/client.rs +53 -31
  30. data/vendor/wreq/src/conn/connector.rs +99 -129
  31. data/vendor/wreq/src/conn/http.rs +25 -18
  32. data/vendor/wreq/src/conn/net/tcp.rs +601 -107
  33. data/vendor/wreq/src/conn/proxy/socks.rs +6 -6
  34. data/vendor/wreq/src/conn/timeout.rs +166 -0
  35. data/vendor/wreq/src/conn.rs +5 -4
  36. data/vendor/wreq/src/cookie/jar.rs +1225 -0
  37. data/vendor/wreq/src/cookie/store.rs +321 -0
  38. data/vendor/wreq/src/cookie.rs +108 -612
  39. data/vendor/wreq/src/dns/resolve.rs +8 -2
  40. data/vendor/wreq/src/dns.rs +4 -4
  41. data/vendor/wreq/src/error.rs +53 -20
  42. data/vendor/wreq/src/lib.rs +1 -0
  43. data/vendor/wreq/src/proxy/matcher.rs +26 -12
  44. data/vendor/wreq/src/proxy/win.rs +39 -9
  45. data/vendor/wreq/src/redirect.rs +515 -100
  46. data/vendor/wreq/src/tls/conn.rs +3 -11
  47. data/vendor/wreq/src/tls/session.rs +7 -8
  48. data/vendor/wreq/src/tls/trust/store.rs +4 -4
  49. data/vendor/wreq/src/util.rs +23 -0
  50. data/vendor/wreq/tests/badssl.rs +72 -7
  51. data/vendor/wreq/tests/brotli.rs +1 -1
  52. data/vendor/wreq/tests/client.rs +24 -0
  53. data/vendor/wreq/tests/connector_layers.rs +8 -4
  54. data/vendor/wreq/tests/cookie.rs +59 -0
  55. data/vendor/wreq/tests/deflate.rs +1 -1
  56. data/vendor/wreq/tests/gzip.rs +53 -1
  57. data/vendor/wreq/tests/layers.rs +8 -4
  58. data/vendor/wreq/tests/redirect.rs +180 -97
  59. data/vendor/wreq/tests/timeouts.rs +47 -12
  60. data/vendor/wreq/tests/zstd.rs +1 -1
  61. metadata +7 -1
@@ -1,4 +1,19 @@
1
1
  //! TCP connection types and utilities.
2
+ //!
3
+ //! DNS resolution finishes before this module receives the candidate addresses.
4
+ //! For that address list, Happy Eyeballs follows curl's connection strategy:
5
+ //! address families are alternated, later attempts are staggered, and the next
6
+ //! address starts immediately when no attempt remains active. Up to six attempts
7
+ //! are kept in flight, and the first successful connection cancels the rest.
8
+ //!
9
+ //! `connect_timeout` is one deadline for the complete TCP race instead of a
10
+ //! separate timeout for each address. When Happy Eyeballs is disabled, addresses
11
+ //! are tried sequentially in resolver order.
12
+ //!
13
+ //! See [RFC 8305 section 5] and [curl's Happy Eyeballs implementation].
14
+ //!
15
+ //! [RFC 8305 section 5]: https://www.rfc-editor.org/rfc/rfc8305.html#section-5
16
+ //! [curl's Happy Eyeballs implementation]: https://github.com/curl/curl/blob/master/lib/cf-ip-happy.c
2
17
 
3
18
  #[cfg(feature = "tokio-rt")]
4
19
  pub mod tokio;
@@ -7,12 +22,14 @@ pub mod tokio;
7
22
  pub mod compio;
8
23
 
9
24
  use std::{
25
+ collections::VecDeque,
10
26
  error::Error as StdError,
11
27
  fmt,
12
28
  future::Future,
13
29
  io,
14
30
  net::{Ipv4Addr, Ipv6Addr, SocketAddr},
15
31
  pin::{Pin, pin},
32
+ task::{Context, Poll},
16
33
  time::Duration,
17
34
  };
18
35
 
@@ -27,6 +44,8 @@ use crate::{
27
44
 
28
45
  type BoxConnecting<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
29
46
 
47
+ const MAX_PARALLEL_CONNECT_ATTEMPTS: usize = 6;
48
+
30
49
  /// A builder for tcp connections.
31
50
  pub trait TcpConnector: Clone + Send + Sync + 'static {
32
51
  /// The underlying stream type.
@@ -44,7 +63,7 @@ pub trait TcpConnector: Clone + Send + Sync + 'static {
44
63
  type Error: Into<Box<dyn StdError + Send + Sync>>;
45
64
 
46
65
  /// The future type returned by this builder.
47
- type Future: Future<Output = Result<Self::Connection, Self::Error>> + Send + 'static;
66
+ type Future: Future<Output = Result<Self::Connection, Self::Error>> + Send + Unpin + 'static;
48
67
 
49
68
  /// The future type returned by this builder's sleep.
50
69
  type Sleep: Future<Output = ()> + Send + 'static;
@@ -72,6 +91,35 @@ struct ConnectingTcpRemote<S: TcpConnector> {
72
91
  connector: S,
73
92
  }
74
93
 
94
+ /// Schedules staggered connection attempts for a resolved address list.
95
+ ///
96
+ /// The state owns every active future, so dropping it cancels the remaining
97
+ /// attempts.
98
+ struct ConnectingTcpState<S: TcpConnector> {
99
+ preferred: ConnectingTcpRemote<S>,
100
+ fallback: Option<ConnectingTcpRemote<S>>,
101
+ initial_delay: Option<S::Sleep>,
102
+ next_fallback: bool,
103
+ happy_eyeballs_timeout: Option<Duration>,
104
+ attempts: VecDeque<ConnectingTcpAttempt<S>>,
105
+ next_attempt_order: usize,
106
+ first_error: Option<(usize, ConnectError)>,
107
+ }
108
+
109
+ /// One active TCP connection attempt and its position in launch order.
110
+ struct ConnectingTcpAttempt<S: TcpConnector> {
111
+ addr: SocketAddr,
112
+ order: usize,
113
+ future: S::Future,
114
+ }
115
+
116
+ /// An event that advances the address race.
117
+ enum TcpEvent<C> {
118
+ Connected(C),
119
+ AllAttemptsFailed,
120
+ DelayElapsed,
121
+ }
122
+
75
123
  impl<S: TcpConnector> ConnectingTcp<S>
76
124
  where
77
125
  S::TcpStream: From<socket2::Socket>,
@@ -119,6 +167,19 @@ where
119
167
  }
120
168
  }
121
169
  }
170
+
171
+ /// Connects through the sequential fast path or the staggered address race.
172
+ pub(crate) async fn connect(self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
173
+ if self.fallback.is_none()
174
+ && (config.happy_eyeballs_timeout.is_none() || self.preferred.addrs.len() <= 1)
175
+ {
176
+ return self.preferred.connect(config).await;
177
+ }
178
+
179
+ ConnectingTcpState::new(self, config.happy_eyeballs_timeout)
180
+ .connect(config)
181
+ .await
182
+ }
122
183
  }
123
184
 
124
185
  impl<S: TcpConnector> ConnectingTcpRemote<S>
@@ -126,7 +187,6 @@ where
126
187
  S::TcpStream: From<socket2::Socket>,
127
188
  {
128
189
  fn new(addrs: dns::SocketAddrs, connect_timeout: Option<Duration>, connector: S) -> Self {
129
- let connect_timeout = connect_timeout.and_then(|t| t.checked_div(addrs.len() as u32));
130
190
  Self {
131
191
  addrs,
132
192
  connect_timeout,
@@ -134,44 +194,272 @@ where
134
194
  }
135
195
  }
136
196
 
137
- async fn connect(&mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
138
- let mut err = None;
139
- for addr in &mut self.addrs {
140
- debug!("connecting to {}", addr);
141
- match connect(&addr, config, self.connect_timeout, &self.connector) {
142
- Ok(fut) => match fut.await {
143
- Ok(tcp) => {
144
- debug!("connected to {}", addr);
145
- return Ok(tcp);
197
+ /// Prepares a connection future for the next address.
198
+ fn connect_next(
199
+ &mut self,
200
+ config: &TcpOptions,
201
+ ) -> Option<(SocketAddr, Result<S::Future, ConnectError>)> {
202
+ let addr = self.addrs.next()?;
203
+ debug!("connecting to {}", addr);
204
+ Some((addr, connect(&addr, config, &self.connector)))
205
+ }
206
+
207
+ /// Tries this address list sequentially under one shared deadline.
208
+ async fn connect(mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
209
+ let timeout = self
210
+ .connect_timeout
211
+ .map(|duration| self.connector.sleep(duration));
212
+ connect_with_timeout(self.connect_inner(config), timeout).await
213
+ }
214
+
215
+ /// Tries addresses in resolver order until one connects.
216
+ async fn connect_inner(&mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
217
+ let mut first_error = None;
218
+
219
+ while let Some((addr, result)) = self.connect_next(config) {
220
+ let result = match result {
221
+ Ok(future) => future.await.map_err(ConnectError::tcp),
222
+ Err(error) => Err(error),
223
+ };
224
+
225
+ match result {
226
+ Ok(connection) => {
227
+ debug!("connected to {}", addr);
228
+ return Ok(connection);
229
+ }
230
+ Err(error) => {
231
+ let error = error.with_addr(addr);
232
+ trace!("connect error for {}: {:?}", addr, error);
233
+ if first_error.is_none() {
234
+ first_error = Some(error);
146
235
  }
147
- Err(mut e) => {
148
- trace!("connect error for {}: {:?}", addr, e);
149
- e.addr = Some(addr);
150
- if err.is_none() {
151
- err = Some(e);
152
- }
236
+ }
237
+ }
238
+ }
239
+
240
+ Err(first_error.unwrap_or_else(ConnectError::network_unreachable))
241
+ }
242
+ }
243
+
244
+ impl<S: TcpConnector> ConnectingTcpState<S>
245
+ where
246
+ S::TcpStream: From<socket2::Socket>,
247
+ {
248
+ /// Builds the scheduler while preserving the initial fallback delay.
249
+ fn new(connecting: ConnectingTcp<S>, happy_eyeballs_timeout: Option<Duration>) -> Self {
250
+ let (initial_delay, fallback) = match connecting.fallback {
251
+ Some(fallback) => (Some(fallback.delay), Some(fallback.remote)),
252
+ None => (None, None),
253
+ };
254
+
255
+ Self {
256
+ preferred: connecting.preferred,
257
+ fallback,
258
+ initial_delay,
259
+ next_fallback: false,
260
+ happy_eyeballs_timeout,
261
+ attempts: VecDeque::with_capacity(MAX_PARALLEL_CONNECT_ATTEMPTS),
262
+ next_attempt_order: 0,
263
+ first_error: None,
264
+ }
265
+ }
266
+
267
+ /// Returns whether any resolved address has not been attempted.
268
+ fn has_remaining_addrs(&self) -> bool {
269
+ !self.preferred.addrs.is_empty()
270
+ || self
271
+ .fallback
272
+ .as_ref()
273
+ .is_some_and(|fallback| !fallback.addrs.is_empty())
274
+ }
275
+
276
+ /// Alternates address families while both still have candidates.
277
+ fn next_remote(&mut self) -> Option<&mut ConnectingTcpRemote<S>> {
278
+ let has_preferred = !self.preferred.addrs.is_empty();
279
+ let has_fallback = self
280
+ .fallback
281
+ .as_ref()
282
+ .is_some_and(|fallback| !fallback.addrs.is_empty());
283
+
284
+ let use_fallback = match (self.next_fallback, has_preferred, has_fallback) {
285
+ (_, false, false) => return None,
286
+ (true, _, true) | (_, false, true) => true,
287
+ _ => false,
288
+ };
289
+ self.next_fallback = !use_fallback;
290
+
291
+ if use_fallback {
292
+ self.fallback.as_mut()
293
+ } else {
294
+ Some(&mut self.preferred)
295
+ }
296
+ }
297
+
298
+ /// Keeps the error from the earliest failed attempt.
299
+ fn record_error(&mut self, order: usize, error: ConnectError) {
300
+ match &self.first_error {
301
+ Some((first_order, _)) if *first_order <= order => {}
302
+ _ => self.first_error = Some((order, error)),
303
+ }
304
+ }
305
+
306
+ /// Starts the next candidate, skipping synchronous socket setup failures.
307
+ fn launch_next(&mut self, config: &TcpOptions) -> bool {
308
+ while let Some(remote) = self.next_remote() {
309
+ let Some((addr, result)) = remote.connect_next(config) else {
310
+ continue;
311
+ };
312
+ let order = self.next_attempt_order;
313
+ self.next_attempt_order = self.next_attempt_order.saturating_add(1);
314
+
315
+ match result {
316
+ Ok(future) => {
317
+ if order != 0 {
318
+ self.initial_delay = None;
153
319
  }
154
- },
155
- Err(mut e) => {
156
- trace!("connect error for {}: {:?}", addr, e);
157
- e.addr = Some(addr);
158
- if err.is_none() {
159
- err = Some(e);
320
+ self.attempts.push_back(ConnectingTcpAttempt {
321
+ addr,
322
+ order,
323
+ future,
324
+ });
325
+ return true;
326
+ }
327
+ Err(error) => {
328
+ let error = error.with_addr(addr);
329
+ trace!("connect error for {}: {:?}", addr, error);
330
+ self.record_error(order, error);
331
+ }
332
+ }
333
+ }
334
+
335
+ false
336
+ }
337
+
338
+ /// Polls every active attempt and reports failure only when none remain.
339
+ fn poll_attempts(&mut self, cx: &mut Context<'_>) -> Poll<TcpEvent<S::Connection>> {
340
+ let mut index = 0;
341
+ let mut failed = false;
342
+ while let Some(attempt) = self.attempts.get_mut(index) {
343
+ let addr = attempt.addr;
344
+ let order = attempt.order;
345
+ let result = Pin::new(&mut attempt.future).poll(cx);
346
+
347
+ match result {
348
+ Poll::Pending => index += 1,
349
+ Poll::Ready(result) => {
350
+ let _ = self.attempts.remove(index);
351
+ match result {
352
+ Ok(connection) => {
353
+ debug!("connected to {}", addr);
354
+ return Poll::Ready(TcpEvent::Connected(connection));
355
+ }
356
+ Err(error) => {
357
+ let error = ConnectError::tcp(error).with_addr(addr);
358
+ trace!("connect error for {}: {:?}", addr, error);
359
+ self.record_error(order, error);
360
+ failed = true;
361
+ }
160
362
  }
161
363
  }
162
364
  }
163
365
  }
164
366
 
165
- match err {
166
- Some(e) => Err(e),
167
- None => Err(ConnectError::new(
168
- "tcp connect error",
169
- std::io::Error::new(std::io::ErrorKind::NotConnected, "Network unreachable"),
170
- )),
367
+ if failed && self.attempts.is_empty() {
368
+ Poll::Ready(TcpEvent::AllAttemptsFailed)
369
+ } else {
370
+ Poll::Pending
371
+ }
372
+ }
373
+
374
+ /// Cancels the oldest attempt when the parallel limit is reached.
375
+ fn make_room_for_next_attempt(&mut self) {
376
+ if self.attempts.len() < MAX_PARALLEL_CONNECT_ATTEMPTS {
377
+ return;
378
+ }
379
+
380
+ if let Some(attempt) = self.attempts.pop_front() {
381
+ trace!("canceling stale connection attempt to {}", attempt.addr);
382
+ drop(attempt);
383
+ }
384
+ }
385
+
386
+ /// Takes the earliest error or reports that no address was reachable.
387
+ fn take_error(&mut self) -> ConnectError {
388
+ self.first_error
389
+ .take()
390
+ .map(|(_, error)| error)
391
+ .unwrap_or_else(ConnectError::network_unreachable)
392
+ }
393
+
394
+ /// Runs the complete address race under one shared deadline.
395
+ async fn connect(mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
396
+ // One deadline covers the complete address race. Dividing it by the
397
+ // number of resolved addresses can make every attempt unusably short.
398
+ let timeout = self
399
+ .preferred
400
+ .connect_timeout
401
+ .map(|duration| self.preferred.connector.sleep(duration));
402
+ connect_with_timeout(self.connect_inner(config), timeout).await
403
+ }
404
+
405
+ /// Drives staggered attempts until one connects or all addresses are exhausted.
406
+ async fn connect_inner(&mut self, config: &TcpOptions) -> Result<S::Connection, ConnectError> {
407
+ loop {
408
+ if self.attempts.is_empty() && !self.launch_next(config) {
409
+ return Err(self.take_error());
410
+ }
411
+
412
+ // RFC 8305 section 5 starts later addresses after a short delay
413
+ // while earlier attempts remain active.
414
+ // https://www.rfc-editor.org/rfc/rfc8305.html#section-5
415
+ let event = match (self.happy_eyeballs_timeout, self.has_remaining_addrs()) {
416
+ (Some(delay), true) => {
417
+ let sleep = self
418
+ .initial_delay
419
+ .take()
420
+ .unwrap_or_else(|| self.preferred.connector.sleep(delay));
421
+ let mut sleep = pin!(sleep);
422
+ std::future::poll_fn(|cx| match self.poll_attempts(cx) {
423
+ Poll::Ready(event) => Poll::Ready(event),
424
+ Poll::Pending => sleep.as_mut().poll(cx).map(|()| TcpEvent::DelayElapsed),
425
+ })
426
+ .await
427
+ }
428
+ _ => std::future::poll_fn(|cx| self.poll_attempts(cx)).await,
429
+ };
430
+
431
+ match event {
432
+ TcpEvent::Connected(connection) => return Ok(connection),
433
+ TcpEvent::AllAttemptsFailed => {}
434
+ TcpEvent::DelayElapsed => self.make_room_for_next_attempt(),
435
+ }
436
+
437
+ self.launch_next(config);
171
438
  }
172
439
  }
173
440
  }
174
441
 
442
+ /// Applies one optional deadline to a complete connection operation.
443
+ async fn connect_with_timeout<C, F, T>(connecting: F, timeout: Option<T>) -> Result<C, ConnectError>
444
+ where
445
+ F: Future<Output = Result<C, ConnectError>>,
446
+ T: Future<Output = ()>,
447
+ {
448
+ let Some(timeout) = timeout else {
449
+ return connecting.await;
450
+ };
451
+
452
+ // Poll the connection first so a result ready at the deadline wins the
453
+ // tie, matching Tokio and Tower timeout semantics.
454
+ match futures_util::future::select(pin!(connecting), pin!(timeout)).await {
455
+ Either::Left((result, _)) => result,
456
+ Either::Right(((), _)) => Err(ConnectError::tcp(io::Error::new(
457
+ io::ErrorKind::TimedOut,
458
+ "connect timeout",
459
+ ))),
460
+ }
461
+ }
462
+
175
463
  fn bind_local_address(
176
464
  socket: &socket2::Socket,
177
465
  dst_addr: &SocketAddr,
@@ -203,9 +491,8 @@ fn bind_local_address(
203
491
  fn connect<S: TcpConnector>(
204
492
  addr: &SocketAddr,
205
493
  config: &TcpOptions,
206
- connect_timeout: Option<Duration>,
207
494
  connector: &S,
208
- ) -> Result<impl Future<Output = Result<S::Connection, ConnectError>>, ConnectError>
495
+ ) -> Result<S::Future, ConnectError>
209
496
  where
210
497
  S::TcpStream: From<socket2::Socket>,
211
498
  {
@@ -221,10 +508,10 @@ where
221
508
  .set_nonblocking(true)
222
509
  .map_err(ConnectError::m("tcp set_nonblocking error"))?;
223
510
 
224
- if let Some(tcp_keepalive) = &config.tcp_keepalive.into_tcpkeepalive() {
225
- if let Err(_e) = socket.set_tcp_keepalive(tcp_keepalive) {
226
- warn!("tcp set_keepalive error: {_e}");
227
- }
511
+ if let Some(tcp_keepalive) = &config.tcp_keepalive.into_tcpkeepalive()
512
+ && let Err(_e) = socket.set_tcp_keepalive(tcp_keepalive)
513
+ {
514
+ warn!("tcp set_keepalive error: {_e}");
228
515
  }
229
516
 
230
517
  // That this only works for some socket types, particularly AF_INET sockets.
@@ -283,10 +570,10 @@ where
283
570
  }
284
571
 
285
572
  #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
286
- if let Some(tcp_user_timeout) = &config.tcp_user_timeout {
287
- if let Err(_e) = socket.set_tcp_user_timeout(Some(*tcp_user_timeout)) {
288
- warn!("tcp set_tcp_user_timeout error: {_e}");
289
- }
573
+ if let Some(tcp_user_timeout) = &config.tcp_user_timeout
574
+ && let Err(_e) = socket.set_tcp_user_timeout(Some(*tcp_user_timeout))
575
+ {
576
+ warn!("tcp set_tcp_user_timeout error: {_e}");
290
577
  }
291
578
 
292
579
  bind_local_address(
@@ -297,84 +584,35 @@ where
297
584
  )
298
585
  .map_err(ConnectError::m("tcp bind local error"))?;
299
586
 
300
- if config.reuse_address {
301
- if let Err(_e) = socket.set_reuse_address(true) {
302
- warn!("tcp set_reuse_address error: {_e}");
303
- }
587
+ if config.reuse_address
588
+ && let Err(_e) = socket.set_reuse_address(true)
589
+ {
590
+ warn!("tcp set_reuse_address error: {_e}");
304
591
  }
305
592
 
306
- if let Some(size) = config.send_buffer_size {
307
- if let Err(_e) = socket.set_send_buffer_size(size) {
308
- warn!("tcp set_buffer_size error: {_e}");
309
- }
593
+ if let Some(linger) = config.linger
594
+ && let Err(_e) = socket.set_linger(Some(linger))
595
+ {
596
+ warn!("tcp set_linger error: {_e}");
310
597
  }
311
598
 
312
- if let Some(size) = config.recv_buffer_size {
313
- if let Err(_e) = socket.set_recv_buffer_size(size) {
314
- warn!("tcp set_recv_buffer_size error: {_e}");
315
- }
599
+ if let Some(size) = config.send_buffer_size
600
+ && let Err(_e) = socket.set_send_buffer_size(size)
601
+ {
602
+ warn!("tcp set_buffer_size error: {_e}");
603
+ }
604
+
605
+ if let Some(size) = config.recv_buffer_size
606
+ && let Err(_e) = socket.set_recv_buffer_size(size)
607
+ {
608
+ warn!("tcp set_recv_buffer_size error: {_e}");
316
609
  }
317
610
 
318
611
  if let Err(_e) = socket.set_tcp_nodelay(config.nodelay) {
319
612
  warn!("tcp set_tcp_nodelay error: {_e}");
320
613
  }
321
614
 
322
- let connect = connector.connect(socket.into(), *addr);
323
- let sleep = connect_timeout.map(|dur| connector.sleep(dur));
324
-
325
- Ok(async move {
326
- match sleep {
327
- Some(sleep) => match futures_util::future::select(pin!(sleep), pin!(connect)).await {
328
- Either::Left(((), _)) => {
329
- Err(io::Error::new(io::ErrorKind::TimedOut, "connect timeout").into())
330
- }
331
- Either::Right((Ok(s), _)) => Ok(s),
332
- Either::Right((Err(e), _)) => Err(e.into()),
333
- },
334
- None => connect.await.map_err(Into::into),
335
- }
336
- .map_err(ConnectError::m("tcp connect error"))
337
- })
338
- }
339
-
340
- impl<S: TcpConnector> ConnectingTcp<S>
341
- where
342
- S::TcpStream: From<socket2::Socket>,
343
- {
344
- pub(crate) async fn connect(
345
- mut self,
346
- config: &TcpOptions,
347
- ) -> Result<S::Connection, ConnectError> {
348
- match self.fallback {
349
- None => self.preferred.connect(config).await,
350
- Some(mut fallback) => {
351
- let preferred_fut = pin!(self.preferred.connect(config));
352
- let fallback_fut = pin!(fallback.remote.connect(config));
353
- let fallback_delay = pin!(fallback.delay);
354
-
355
- let (result, future) =
356
- match futures_util::future::select(preferred_fut, fallback_delay).await {
357
- Either::Left((result, _fallback_delay)) => {
358
- (result, Either::Right(fallback_fut))
359
- }
360
- Either::Right(((), preferred_fut)) => {
361
- // Delay is done, start polling both the preferred and the fallback
362
- futures_util::future::select(preferred_fut, fallback_fut)
363
- .await
364
- .factor_first()
365
- }
366
- };
367
-
368
- if result.is_err() {
369
- // Fallback to the remaining future (could be preferred or fallback)
370
- // if we get an error
371
- future.await
372
- } else {
373
- result
374
- }
375
- }
376
- }
377
- }
615
+ Ok(connector.connect(socket.into(), *addr))
378
616
  }
379
617
 
380
618
  // Not publicly exported (so missing_docs doesn't trigger).
@@ -396,6 +634,22 @@ impl ConnectError {
396
634
  }
397
635
  }
398
636
 
637
+ /// Wraps an error produced while opening a TCP connection.
638
+ fn tcp<E>(cause: E) -> ConnectError
639
+ where
640
+ E: Into<BoxError>,
641
+ {
642
+ ConnectError::new("tcp connect error", cause)
643
+ }
644
+
645
+ /// Creates the fallback error used when no address can be attempted.
646
+ fn network_unreachable() -> ConnectError {
647
+ ConnectError::tcp(io::Error::new(
648
+ io::ErrorKind::NotConnected,
649
+ "Network unreachable",
650
+ ))
651
+ }
652
+
399
653
  pub(crate) fn dns<E>(cause: E) -> ConnectError
400
654
  where
401
655
  E: Into<BoxError>,
@@ -409,6 +663,12 @@ impl ConnectError {
409
663
  {
410
664
  move |cause| ConnectError::new(msg, cause)
411
665
  }
666
+
667
+ /// Attaches the address associated with this connection error.
668
+ fn with_addr(mut self, addr: SocketAddr) -> Self {
669
+ self.addr = Some(addr);
670
+ self
671
+ }
412
672
  }
413
673
 
414
674
  impl fmt::Debug for ConnectError {
@@ -444,6 +704,7 @@ pub(crate) struct TcpOptions {
444
704
  pub happy_eyeballs_timeout: Option<Duration>,
445
705
  pub nodelay: bool,
446
706
  pub reuse_address: bool,
707
+ pub linger: Option<Duration>,
447
708
  pub send_buffer_size: Option<usize>,
448
709
  pub recv_buffer_size: Option<usize>,
449
710
  #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
@@ -559,3 +820,236 @@ impl TcpKeepaliveOptions {
559
820
  if dirty { Some(ka) } else { None }
560
821
  }
561
822
  }
823
+
824
+ #[cfg(test)]
825
+ mod tests {
826
+ use std::{
827
+ io,
828
+ net::{Ipv6Addr, SocketAddr},
829
+ sync::{Arc, Mutex},
830
+ time::Duration,
831
+ };
832
+
833
+ use super::{
834
+ BoxConnecting, ConnectingTcp, TcpConnector, TcpKeepaliveOptions, TcpOptions,
835
+ connect_with_timeout,
836
+ };
837
+ use crate::{
838
+ conn::{Connected, Connection, net::SocketBindOptions},
839
+ dns,
840
+ };
841
+
842
+ #[derive(Default)]
843
+ struct TestState {
844
+ launched: Vec<SocketAddr>,
845
+ active: usize,
846
+ max_active: usize,
847
+ }
848
+
849
+ #[derive(Clone)]
850
+ struct TestConnector {
851
+ outcomes: Arc<[(SocketAddr, TestOutcome)]>,
852
+ state: Arc<Mutex<TestState>>,
853
+ }
854
+
855
+ #[derive(Clone, Copy)]
856
+ enum TestOutcome {
857
+ Pending,
858
+ SuccessAfter(Duration),
859
+ FailAfter(Duration),
860
+ }
861
+
862
+ struct ActiveAttempt(Arc<Mutex<TestState>>);
863
+
864
+ impl Drop for ActiveAttempt {
865
+ fn drop(&mut self) {
866
+ let mut state = self.0.lock().unwrap();
867
+ state.active -= 1;
868
+ }
869
+ }
870
+
871
+ impl TestConnector {
872
+ fn new(success: SocketAddr) -> Self {
873
+ Self::with_outcomes([(success, TestOutcome::SuccessAfter(Duration::ZERO))])
874
+ }
875
+
876
+ fn with_outcomes(outcomes: impl IntoIterator<Item = (SocketAddr, TestOutcome)>) -> Self {
877
+ Self {
878
+ outcomes: outcomes.into_iter().collect::<Vec<_>>().into(),
879
+ state: Arc::new(Mutex::new(TestState::default())),
880
+ }
881
+ }
882
+
883
+ fn snapshot(&self) -> (Vec<SocketAddr>, usize, usize) {
884
+ let state = self.state.lock().unwrap();
885
+ (state.launched.clone(), state.active, state.max_active)
886
+ }
887
+ }
888
+
889
+ impl TcpConnector for TestConnector {
890
+ type TcpStream = std::net::TcpStream;
891
+ type Connection = ::tokio::io::DuplexStream;
892
+ type Error = io::Error;
893
+ type Future = BoxConnecting<Self::Connection, Self::Error>;
894
+ type Sleep = ::tokio::time::Sleep;
895
+
896
+ fn connect(&self, _socket: Self::TcpStream, addr: SocketAddr) -> Self::Future {
897
+ {
898
+ let mut state = self.state.lock().unwrap();
899
+ state.launched.push(addr);
900
+ state.active += 1;
901
+ state.max_active = state.max_active.max(state.active);
902
+ }
903
+ let outcome = self
904
+ .outcomes
905
+ .iter()
906
+ .find_map(|(candidate, outcome)| (*candidate == addr).then_some(*outcome))
907
+ .unwrap_or(TestOutcome::Pending);
908
+ let attempt = ActiveAttempt(self.state.clone());
909
+
910
+ Box::pin(async move {
911
+ let _attempt = attempt;
912
+ match outcome {
913
+ TestOutcome::Pending => std::future::pending().await,
914
+ TestOutcome::SuccessAfter(delay) => {
915
+ ::tokio::time::sleep(delay).await;
916
+ Ok(::tokio::io::duplex(64).0)
917
+ }
918
+ TestOutcome::FailAfter(delay) => {
919
+ ::tokio::time::sleep(delay).await;
920
+ Err(io::ErrorKind::ConnectionRefused.into())
921
+ }
922
+ }
923
+ })
924
+ }
925
+
926
+ fn sleep(&self, duration: Duration) -> Self::Sleep {
927
+ ::tokio::time::sleep(duration)
928
+ }
929
+ }
930
+
931
+ impl Connection for ::tokio::io::DuplexStream {
932
+ fn connected(&self) -> Connected {
933
+ Connected::new()
934
+ }
935
+ }
936
+
937
+ fn tcp_options(
938
+ happy_eyeballs_timeout: Option<Duration>,
939
+ connect_timeout: Option<Duration>,
940
+ ) -> TcpOptions {
941
+ TcpOptions {
942
+ enforce_http: false,
943
+ connect_timeout,
944
+ happy_eyeballs_timeout,
945
+ nodelay: false,
946
+ reuse_address: false,
947
+ linger: None,
948
+ send_buffer_size: None,
949
+ recv_buffer_size: None,
950
+ #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
951
+ tcp_user_timeout: None,
952
+ tcp_keepalive: TcpKeepaliveOptions::default(),
953
+ socket_bind: SocketBindOptions::default(),
954
+ }
955
+ }
956
+
957
+ fn ipv4(last: u8) -> SocketAddr {
958
+ ([192, 0, 2, last], 443).into()
959
+ }
960
+
961
+ fn ipv6(last: u16) -> SocketAddr {
962
+ (Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, last), 443).into()
963
+ }
964
+
965
+ #[::tokio::test(start_paused = true)]
966
+ async fn races_resolved_addresses_in_order_with_a_bounded_set() {
967
+ let result =
968
+ connect_with_timeout(std::future::ready(Ok(())), Some(std::future::ready(()))).await;
969
+ assert!(
970
+ result.is_ok(),
971
+ "a ready connection should win a timeout tie"
972
+ );
973
+
974
+ let delay = Duration::from_millis(100);
975
+ let sequential = [ipv4(1), ipv4(2)];
976
+ let connector = TestConnector::with_outcomes([
977
+ (sequential[0], TestOutcome::FailAfter(delay / 2)),
978
+ (sequential[1], TestOutcome::SuccessAfter(Duration::ZERO)),
979
+ ]);
980
+ let options = tcp_options(None, None);
981
+ let started = ::tokio::time::Instant::now();
982
+
983
+ ConnectingTcp::new(
984
+ dns::SocketAddrs::new(sequential.to_vec()),
985
+ &options,
986
+ connector.clone(),
987
+ )
988
+ .connect(&options)
989
+ .await
990
+ .unwrap();
991
+
992
+ assert_eq!(started.elapsed(), delay / 2);
993
+ assert_eq!(connector.snapshot(), (sequential.to_vec(), 0, 1));
994
+
995
+ let paced = [ipv4(1), ipv4(2), ipv4(3)];
996
+ let connector = TestConnector::with_outcomes([
997
+ (paced[1], TestOutcome::FailAfter(delay / 10)),
998
+ (paced[2], TestOutcome::SuccessAfter(Duration::ZERO)),
999
+ ]);
1000
+ let options = tcp_options(Some(delay), None);
1001
+ let started = ::tokio::time::Instant::now();
1002
+
1003
+ ConnectingTcp::new(
1004
+ dns::SocketAddrs::new(paced.to_vec()),
1005
+ &options,
1006
+ connector.clone(),
1007
+ )
1008
+ .connect(&options)
1009
+ .await
1010
+ .unwrap();
1011
+
1012
+ assert_eq!(started.elapsed(), delay * 2);
1013
+ assert_eq!(connector.snapshot(), (paced.to_vec(), 0, 2));
1014
+
1015
+ let v4 = [ipv4(1), ipv4(2), ipv4(3), ipv4(4), ipv4(5), ipv4(6)];
1016
+ let v6 = [ipv6(1), ipv6(2)];
1017
+ let addrs = [v4.as_slice(), v6.as_slice()].concat();
1018
+ let expected = [v4[0], v6[0], v4[1], v6[1], v4[2], v4[3], v4[4], v4[5]];
1019
+ let connector = TestConnector::new(v4[5]);
1020
+ let started = ::tokio::time::Instant::now();
1021
+
1022
+ ConnectingTcp::new(dns::SocketAddrs::new(addrs), &options, connector.clone())
1023
+ .connect(&options)
1024
+ .await
1025
+ .unwrap();
1026
+
1027
+ assert_eq!(started.elapsed(), delay * 7);
1028
+ assert_eq!(connector.snapshot(), (expected.to_vec(), 0, 6));
1029
+
1030
+ let timeout = Duration::from_millis(250);
1031
+ let options = tcp_options(Some(delay), Some(timeout));
1032
+ let addrs = [ipv4(1), ipv4(2), ipv4(3), ipv4(4)];
1033
+ let connector = TestConnector::new(ipv4(255));
1034
+ let started = ::tokio::time::Instant::now();
1035
+
1036
+ let error = ConnectingTcp::new(
1037
+ dns::SocketAddrs::new(addrs.to_vec()),
1038
+ &options,
1039
+ connector.clone(),
1040
+ )
1041
+ .connect(&options)
1042
+ .await
1043
+ .unwrap_err();
1044
+
1045
+ assert_eq!(started.elapsed(), timeout);
1046
+ assert!(
1047
+ error
1048
+ .cause
1049
+ .as_deref()
1050
+ .and_then(|cause| cause.downcast_ref::<io::Error>())
1051
+ .is_some_and(|cause| cause.kind() == io::ErrorKind::TimedOut)
1052
+ );
1053
+ assert_eq!(connector.snapshot(), (addrs[..3].to_vec(), 0, 3));
1054
+ }
1055
+ }