Skip to content

Commit 605a93b

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 c691140 commit 605a93b

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

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

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

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

0 commit comments

Comments
 (0)