@@ -24,6 +24,12 @@ use std::net::TcpStream;
2424/// Timeout for operations on TCP streams.
2525const TCP_STREAM_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
2626
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+
2733/// Maximum HTTP message header size in bytes.
2834const MAX_HTTP_MESSAGE_HEADER_SIZE : usize = 8192 ;
2935
@@ -209,25 +215,44 @@ impl HttpClient {
209215 #[ cfg( not( feature = "tokio" ) ) ]
210216 let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
211217
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+ }
228252
229253 // 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( ) )
231256 . ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
232257 let status = HttpStatus :: parse ( & status_line) ?;
233258
0 commit comments