@@ -24,6 +24,12 @@ use std::net::TcpStream;
24
24
/// Timeout for operations on TCP streams.
25
25
const TCP_STREAM_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
26
26
27
+ /// Timeout for reading the first byte of a response. This is separate as it is not uncommon for
28
+ /// Bitcoin Core to be blocked waiting on UTXO cache flushes for upwards of a minute or more. Note
29
+ /// that we always retry once when we time out, so the maximum time we allow Bitcoin Core to block
30
+ /// for is twice this value.
31
+ const TCP_STREAM_RESPONSE_TIMEOUT : Duration = Duration :: from_secs ( 120 ) ;
32
+
27
33
/// Maximum HTTP message header size in bytes.
28
34
const MAX_HTTP_MESSAGE_HEADER_SIZE : usize = 8192 ;
29
35
@@ -207,25 +213,45 @@ impl HttpClient {
207
213
#[ cfg( not( feature = "tokio" ) ) ]
208
214
let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
209
215
210
- macro_rules! read_line { ( ) => { {
211
- let mut line = String :: new( ) ;
212
- #[ cfg( feature = "tokio" ) ]
213
- let bytes_read = reader. read_line( & mut line) . await ?;
214
- #[ cfg( not( feature = "tokio" ) ) ]
215
- let bytes_read = reader. read_line( & mut line) ?;
216
-
217
- match bytes_read {
218
- 0 => None ,
219
- _ => {
220
- // Remove trailing CRLF
221
- if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
222
- Some ( line)
223
- } ,
216
+ macro_rules! read_line {
217
+ ( ) => { read_line!( 0 ) } ;
218
+ ( $retry_count: expr) => { {
219
+ let mut line = String :: new( ) ;
220
+ let mut timeout_count: u64 = 0 ;
221
+ let bytes_read = loop {
222
+ #[ cfg( feature = "tokio" ) ]
223
+ let read_res = reader. read_line( & mut line) . await ;
224
+ #[ cfg( not( feature = "tokio" ) ) ]
225
+ let read_res = reader. read_line( & mut line) ;
226
+ match read_res {
227
+ Ok ( bytes_read) => break bytes_read,
228
+ Err ( e) if e. kind( ) == std:: io:: ErrorKind :: WouldBlock => {
229
+ timeout_count += 1 ;
230
+ if timeout_count > $retry_count {
231
+ return Err ( e) ;
232
+ } else {
233
+ continue ;
234
+ }
235
+ }
236
+ Err ( e) => return Err ( e) ,
237
+ }
238
+ } ;
239
+
240
+ match bytes_read {
241
+ 0 => None ,
242
+ _ => {
243
+ // Remove trailing CRLF
244
+ if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
245
+ Some ( line)
246
+ } ,
247
+ }
224
248
}
225
- } } }
249
+ } }
226
250
227
251
// Read and parse status line
228
- let status_line = read_line ! ( )
252
+ // Note that we first reset the read timeout to give us extra time in case the responding
253
+ // node is blocked waiting on some slow I/O operation such as UTXO cache flushing.
254
+ let status_line = read_line ! ( TCP_STREAM_RESPONSE_TIMEOUT . as_secs( ) / TCP_STREAM_TIMEOUT . as_secs( ) )
229
255
. ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
230
256
let status = HttpStatus :: parse ( & status_line) ?;
231
257
0 commit comments