Skip to content

Commit 8da44a9

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 c9b12e1 commit 8da44a9

File tree

1 file changed

+42
-16
lines changed

1 file changed

+42
-16
lines changed

lightning-block-sync/src/http.rs

Lines changed: 42 additions & 16 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 as it is not uncommon for
28+
/// Bitcoin Core to be blocked waiting on UTXO cache flushes for upwards of a minute or more. Note
29+
/// that we always retry once when we time out, so the maximum time we allow Bitcoin Core to block
30+
/// 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

@@ -206,25 +212,45 @@ impl HttpClient {
206212
#[cfg(not(feature = "tokio"))]
207213
let mut reader = std::io::BufReader::new(limited_stream);
208214

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-
},
215+
macro_rules! read_line {
216+
() => { read_line!(0) };
217+
($retry_count: expr) => { {
218+
let mut line = String::new();
219+
let mut timeout_count: u64 = 0;
220+
let bytes_read = loop {
221+
#[cfg(feature = "tokio")]
222+
let read_res = reader.read_line(&mut line).await;
223+
#[cfg(not(feature = "tokio"))]
224+
let read_res = reader.read_line(&mut line);
225+
match read_res {
226+
Ok(bytes_read) => break bytes_read,
227+
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
228+
timeout_count += 1;
229+
if timeout_count > $retry_count {
230+
return Err(e);
231+
} else {
232+
continue;
233+
}
234+
}
235+
Err(e) => return Err(e),
236+
}
237+
};
238+
239+
match bytes_read {
240+
0 => None,
241+
_ => {
242+
// Remove trailing CRLF
243+
if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { line.pop(); } }
244+
Some(line)
245+
},
246+
}
223247
}
224-
} } }
248+
} }
225249

226250
// Read and parse status line
227-
let status_line = read_line!()
251+
// Note that we first reset the read timeout to give us extra time in case the responding
252+
// node is blocked waiting on some slow I/O operation such as UTXO cache flushing.
253+
let status_line = read_line!(TCP_STREAM_RESPONSE_TIMEOUT.as_secs() / TCP_STREAM_TIMEOUT.as_secs())
228254
.ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "no status line"))?;
229255
let status = HttpStatus::parse(&status_line)?;
230256

0 commit comments

Comments
 (0)