Skip to content

Commit 4ade6bc

Browse files
committed
Increase the timeout for RPC responses from Bitcoin Core
Early sample testing showed multiple users hitting EWOULDBLOCK/EAGAIN waiting for an initial response from Bitcoin Core while it was doing some long operation (eg UTXO cache flushing). Instead of only waiting 5 seconds for each attempt, we now wait a full two minutes, but only for the first header response, not each byte.
1 parent b465318 commit 4ade6bc

File tree

1 file changed

+42
-17
lines changed

1 file changed

+42
-17
lines changed

lightning-block-sync/src/http.rs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ use std::net::TcpStream;
2424
/// Timeout for operations on TCP streams.
2525
const 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.
2834
const 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

Comments
 (0)