@@ -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
@@ -206,25 +212,45 @@ impl HttpClient {
206
212
#[ cfg( not( feature = "tokio" ) ) ]
207
213
let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
208
214
209
- macro_rules! read_line { ( ) => { {
210
- let mut line = String :: new( ) ;
211
- #[ cfg( feature = "tokio" ) ]
212
- let bytes_read = reader. read_line( & mut line) . await ?;
213
- #[ cfg( not( feature = "tokio" ) ) ]
214
- let bytes_read = reader. read_line( & mut line) ?;
215
-
216
- match bytes_read {
217
- 0 => None ,
218
- _ => {
219
- // Remove trailing CRLF
220
- if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
221
- Some ( line)
222
- } ,
215
+ macro_rules! read_line {
216
+ ( ) => { read_line!( 0 ) } ;
217
+ ( $retry_count: expr) => { {
218
+ let mut line = String :: new( ) ;
219
+ let mut timeout_count: u64 = 0 ;
220
+ let bytes_read = loop {
221
+ #[ cfg( feature = "tokio" ) ]
222
+ let read_res = reader. read_line( & mut line) . await ;
223
+ #[ cfg( not( feature = "tokio" ) ) ]
224
+ let read_res = reader. read_line( & mut line) ;
225
+ match read_res {
226
+ Ok ( bytes_read) => break bytes_read,
227
+ Err ( e) if e. kind( ) == std:: io:: ErrorKind :: WouldBlock => {
228
+ timeout_count += 1 ;
229
+ if timeout_count > $retry_count {
230
+ return Err ( e) ;
231
+ } else {
232
+ continue ;
233
+ }
234
+ }
235
+ Err ( e) => return Err ( e) ,
236
+ }
237
+ } ;
238
+
239
+ match bytes_read {
240
+ 0 => None ,
241
+ _ => {
242
+ // Remove trailing CRLF
243
+ if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
244
+ Some ( line)
245
+ } ,
246
+ }
223
247
}
224
- } } }
248
+ } }
225
249
226
250
// Read and parse status line
227
- let status_line = read_line ! ( )
251
+ // Note that we first reset the read timeout to give us extra time in case the responding
252
+ // node is blocked waiting on some slow I/O operation such as UTXO cache flushing.
253
+ let status_line = read_line ! ( TCP_STREAM_RESPONSE_TIMEOUT . as_secs( ) / TCP_STREAM_TIMEOUT . as_secs( ) )
228
254
. ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
229
255
let status = HttpStatus :: parse ( & status_line) ?;
230
256
0 commit comments