5
5
use std:: collections:: { HashMap , HashSet , VecDeque } ;
6
6
use std:: io:: { BufRead , BufReader , Read , Write } ;
7
7
use std:: mem:: drop;
8
- use std:: net:: { SocketAddr , TcpStream , ToSocketAddrs } ;
8
+ use std:: net:: { TcpStream , ToSocketAddrs } ;
9
9
use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
10
10
use std:: sync:: mpsc:: { channel, Receiver , Sender } ;
11
11
use std:: sync:: { Arc , Mutex , TryLockError } ;
@@ -161,8 +161,7 @@ impl RawClient<ElectrumPlaintextStream> {
161
161
) -> Result < Self , Error > {
162
162
let stream = match timeout {
163
163
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) ?;
166
165
stream. set_read_timeout ( Some ( timeout) ) ?;
167
166
stream. set_write_timeout ( Some ( timeout) ) ?;
168
167
stream
@@ -174,16 +173,41 @@ impl RawClient<ElectrumPlaintextStream> {
174
173
}
175
174
}
176
175
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
+ }
186
208
}
209
+
210
+ Err ( Error :: AllAttemptsErrored ( errors) )
187
211
}
188
212
189
213
#[ cfg( feature = "use-openssl" ) ]
@@ -209,8 +233,7 @@ impl RawClient<ElectrumSslStream> {
209
233
}
210
234
match timeout {
211
235
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) ?;
214
237
stream. set_read_timeout ( Some ( timeout) ) ?;
215
238
stream. set_write_timeout ( Some ( timeout) ) ?;
216
239
Self :: new_ssl_from_stream ( socket_addrs, validate_domain, stream)
@@ -300,8 +323,7 @@ impl RawClient<ElectrumSslStream> {
300
323
}
301
324
match timeout {
302
325
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) ?;
305
327
stream. set_read_timeout ( Some ( timeout) ) ?;
306
328
stream. set_write_timeout ( Some ( timeout) ) ?;
307
329
Self :: new_ssl_from_stream ( socket_addrs, validate_domain, stream)
0 commit comments