Skip to content

Commit fa9359c

Browse files
committed
Implement timeouts when there are multiple socket addrs
1 parent 25dc3fa commit fa9359c

File tree

2 files changed

+38
-19
lines changed

2 files changed

+38
-19
lines changed

src/raw_client.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::collections::{HashMap, HashSet, VecDeque};
66
use std::io::{BufRead, BufReader, Read, Write};
77
use std::mem::drop;
8-
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
8+
use std::net::{TcpStream, ToSocketAddrs};
99
use std::sync::atomic::{AtomicUsize, Ordering};
1010
use std::sync::mpsc::{channel, Receiver, Sender};
1111
use std::sync::{Arc, Mutex, TryLockError};
@@ -161,8 +161,7 @@ impl RawClient<ElectrumPlaintextStream> {
161161
) -> Result<Self, Error> {
162162
let stream = match timeout {
163163
Some(timeout) => {
164-
let socket_addr = get_one_socket_addr(socket_addrs)?;
165-
let stream = TcpStream::connect_timeout(&socket_addr, timeout)?;
164+
let stream = connect_with_total_timeout(socket_addrs, timeout)?;
166165
stream.set_read_timeout(Some(timeout))?;
167166
stream.set_write_timeout(Some(timeout))?;
168167
stream
@@ -174,16 +173,41 @@ impl RawClient<ElectrumPlaintextStream> {
174173
}
175174
}
176175

177-
fn get_one_socket_addr<A: ToSocketAddrs>(socket_addrs: A) -> Result<SocketAddr, Error> {
178-
let mut socket_iter = socket_addrs.to_socket_addrs()?;
179-
let socket_addr = socket_iter
180-
.next()
181-
.ok_or(Error::WrongAddrsNumberWithTimeout)?;
182-
// Unlike `connect`, `connect_timeout` takes a single [`SocketAddr`]
183-
match socket_iter.next() {
184-
None => Ok(socket_addr),
185-
Some(_) => Err(Error::WrongAddrsNumberWithTimeout),
176+
fn connect_with_total_timeout<A: ToSocketAddrs>(
177+
socket_addrs: A,
178+
mut timeout: Duration,
179+
) -> Result<TcpStream, Error> {
180+
// Use the same algorithm as curl: 1/2 on the first host, 1/4 on the second one, etc.
181+
// https://curl.se/mail/lib-2014-11/0164.html
182+
183+
let mut errors = Vec::new();
184+
185+
let addrs = socket_addrs
186+
.to_socket_addrs()?
187+
.enumerate()
188+
.collect::<Vec<_>>();
189+
for (index, addr) in &addrs {
190+
if *index < addrs.len() - 1 {
191+
timeout = timeout.div_f32(2.0);
192+
}
193+
194+
info!(
195+
"Trying to connect to {} (attempt {}/{}) with timeout {:?}",
196+
addr,
197+
index + 1,
198+
addrs.len(),
199+
timeout
200+
);
201+
match TcpStream::connect_timeout(addr, timeout) {
202+
Ok(socket) => return Ok(socket),
203+
Err(e) => {
204+
warn!("Connection error: {:?}", e);
205+
errors.push(e.into());
206+
}
207+
}
186208
}
209+
210+
Err(Error::AllAttemptsErrored(errors))
187211
}
188212

189213
#[cfg(feature = "use-openssl")]
@@ -209,8 +233,7 @@ impl RawClient<ElectrumSslStream> {
209233
}
210234
match timeout {
211235
Some(timeout) => {
212-
let socket_addr = get_one_socket_addr(socket_addrs.clone())?;
213-
let stream = TcpStream::connect_timeout(&socket_addr, timeout)?;
236+
let stream = connect_with_total_timeout(socket_addrs.clone(), timeout)?;
214237
stream.set_read_timeout(Some(timeout))?;
215238
stream.set_write_timeout(Some(timeout))?;
216239
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)
@@ -300,8 +323,7 @@ impl RawClient<ElectrumSslStream> {
300323
}
301324
match timeout {
302325
Some(timeout) => {
303-
let socket_addr = get_one_socket_addr(socket_addrs.clone())?;
304-
let stream = TcpStream::connect_timeout(&socket_addr, timeout)?;
326+
let stream = connect_with_total_timeout(socket_addrs.clone(), timeout)?;
305327
stream.set_read_timeout(Some(timeout))?;
306328
stream.set_write_timeout(Some(timeout))?;
307329
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)

src/types.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,6 @@ pub enum Error {
296296
SharedIOError(Arc<std::io::Error>),
297297
/// Setting both a proxy and a timeout in `Config` is an error
298298
BothSocksAndTimeout,
299-
/// Setting both a timeout and passing zero or more than one socket addrs is an error
300-
WrongAddrsNumberWithTimeout,
301299

302300
/// Couldn't take a lock on the reader mutex. This means that there's already another reader
303301
/// thread running
@@ -343,7 +341,6 @@ impl Display for Error {
343341

344342
Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
345343
Error::BothSocksAndTimeout => f.write_str("Setting both a proxy and a timeout in `Config` is an error"),
346-
Error::WrongAddrsNumberWithTimeout => f.write_str("Setting both a timeout and passing zero or more than one socket addrs is an error"),
347344
Error::CouldntLockReader => f.write_str("Couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"),
348345
}
349346
}

0 commit comments

Comments
 (0)