Skip to content

Commit fcc0723

Browse files
authored
Merge pull request #915 from TheBlueMatt/2021-05-bump-rpc-timeout
Increase the timeout for RPC responses from Bitcoin Core
2 parents 7297e13 + 4ade6bc commit fcc0723

File tree

2 files changed

+56
-28
lines changed

2 files changed

+56
-28
lines changed

lightning-block-sync/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ rpc-client = [ "serde", "serde_json", "chunked_transfer" ]
1616
[dependencies]
1717
bitcoin = "0.26"
1818
lightning = { version = "0.0.14", path = "../lightning" }
19-
tokio = { version = "1.0", features = [ "io-util", "net" ], optional = true }
19+
tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true }
2020
serde = { version = "1.0", features = ["derive"], optional = true }
2121
serde_json = { version = "1.0", optional = true }
2222
chunked_transfer = { version = "1.4", optional = true }

lightning-block-sync/src/http.rs

Lines changed: 55 additions & 27 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

@@ -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

Comments
 (0)