@@ -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 from the general read
28
+ /// timeout as it is not uncommon for Bitcoin Core to be blocked waiting on UTXO cache flushes for
29
+ /// upwards of a minute or more. Note that we always retry once when we time out, so the maximum
30
+ /// time we allow Bitcoin Core to block 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
@@ -209,25 +215,44 @@ impl HttpClient {
209
215
#[ cfg( not( feature = "tokio" ) ) ]
210
216
let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
211
217
212
- macro_rules! read_line { ( ) => { {
213
- let mut line = String :: new( ) ;
214
- #[ cfg( feature = "tokio" ) ]
215
- let bytes_read = reader. read_line( & mut line) . await ?;
216
- #[ cfg( not( feature = "tokio" ) ) ]
217
- let bytes_read = reader. read_line( & mut line) ?;
218
-
219
- match bytes_read {
220
- 0 => None ,
221
- _ => {
222
- // Remove trailing CRLF
223
- if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
224
- Some ( line)
225
- } ,
226
- }
227
- } } }
218
+ macro_rules! read_line {
219
+ ( ) => { read_line!( 0 ) } ;
220
+ ( $retry_count: expr) => { {
221
+ let mut line = String :: new( ) ;
222
+ let mut timeout_count: u64 = 0 ;
223
+ let bytes_read = loop {
224
+ #[ cfg( feature = "tokio" ) ]
225
+ let read_res = reader. read_line( & mut line) . await ;
226
+ #[ cfg( not( feature = "tokio" ) ) ]
227
+ let read_res = reader. read_line( & mut line) ;
228
+ match read_res {
229
+ Ok ( bytes_read) => break bytes_read,
230
+ Err ( e) if e. kind( ) == std:: io:: ErrorKind :: WouldBlock => {
231
+ timeout_count += 1 ;
232
+ if timeout_count > $retry_count {
233
+ return Err ( e) ;
234
+ } else {
235
+ continue ;
236
+ }
237
+ }
238
+ Err ( e) => return Err ( e) ,
239
+ }
240
+ } ;
241
+
242
+ match bytes_read {
243
+ 0 => None ,
244
+ _ => {
245
+ // Remove trailing CRLF
246
+ if line. ends_with( '\n' ) { line. pop( ) ; if line. ends_with( '\r' ) { line. pop( ) ; } }
247
+ Some ( line)
248
+ } ,
249
+ }
250
+ } }
251
+ }
228
252
229
253
// Read and parse status line
230
- let status_line = read_line ! ( )
254
+ // Note that we allow retrying a few times to reach TCP_STREAM_RESPONSE_TIMEOUT.
255
+ let status_line = read_line ! ( TCP_STREAM_RESPONSE_TIMEOUT . as_secs( ) / TCP_STREAM_TIMEOUT . as_secs( ) )
231
256
. ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
232
257
let status = HttpStatus :: parse ( & status_line) ?;
233
258
0 commit comments