@@ -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
@@ -158,16 +164,19 @@ impl HttpClient {
158164 let endpoint = self . stream . peer_addr ( ) . unwrap ( ) ;
159165 match self . send_request ( request) . await {
160166 Ok ( bytes) => Ok ( bytes) ,
161- Err ( e) => match e. kind ( ) {
162- std:: io:: ErrorKind :: ConnectionReset |
163- std:: io:: ErrorKind :: ConnectionAborted |
164- std:: io:: ErrorKind :: UnexpectedEof => {
165- // Reconnect if the connection was closed. This may happen if the server's
166- // keep-alive limits are reached.
167- * self = Self :: connect ( endpoint) ?;
168- self . send_request ( request) . await
169- } ,
170- _ => Err ( e) ,
167+ Err ( _) => {
168+ // Reconnect and retry on fail. This can happen if the connection was closed after
169+ // the keep-alive limits are reached, or generally if the request timed out due to
170+ // Bitcoin Core being stuck on a long-running operation or its RPC queue being
171+ // full.
172+ // Block 100ms before retrying the request as in many cases the source of the error
173+ // may be persistent for some time.
174+ #[ cfg( feature = "tokio" ) ]
175+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
176+ #[ cfg( not( feature = "tokio" ) ) ]
177+ std:: thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
178+ * self = Self :: connect ( endpoint) ?;
179+ self . send_request ( request) . await
171180 } ,
172181 }
173182 }
@@ -206,25 +215,44 @@ impl HttpClient {
206215 #[ cfg( not( feature = "tokio" ) ) ]
207216 let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
208217
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- } ,
223- }
224- } } }
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+ }
225252
226253 // Read and parse status line
227- 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( ) )
228256 . ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
229257 let status = HttpStatus :: parse ( & status_line) ?;
230258
0 commit comments