@@ -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
@@ -158,16 +164,19 @@ impl HttpClient {
158
164
let endpoint = self . stream . peer_addr ( ) . unwrap ( ) ;
159
165
match self . send_request ( request) . await {
160
166
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
171
180
} ,
172
181
}
173
182
}
@@ -206,25 +215,44 @@ impl HttpClient {
206
215
#[ cfg( not( feature = "tokio" ) ) ]
207
216
let mut reader = std:: io:: BufReader :: new ( limited_stream) ;
208
217
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
+ }
225
252
226
253
// 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( ) )
228
256
. ok_or ( std:: io:: Error :: new ( std:: io:: ErrorKind :: UnexpectedEof , "no status line" ) ) ?;
229
257
let status = HttpStatus :: parse ( & status_line) ?;
230
258
0 commit comments