From 4ade6bcb69a1b93c9b5ddd991398abbd536906d0 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 6 May 2021 20:42:02 +0000 Subject: [PATCH] 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. --- lightning-block-sync/src/http.rs | 59 +++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/lightning-block-sync/src/http.rs b/lightning-block-sync/src/http.rs index 1f5f046c..2cfb8e50 100644 --- a/lightning-block-sync/src/http.rs +++ b/lightning-block-sync/src/http.rs @@ -24,6 +24,12 @@ use std::net::TcpStream; /// Timeout for operations on TCP streams. const TCP_STREAM_TIMEOUT: Duration = Duration::from_secs(5); +/// Timeout for reading the first byte of a response. This is separate from the general read +/// timeout as it is not uncommon for Bitcoin Core to be blocked waiting on UTXO cache flushes for +/// upwards of a minute or more. Note that we always retry once when we time out, so the maximum +/// time we allow Bitcoin Core to block for is twice this value. +const TCP_STREAM_RESPONSE_TIMEOUT: Duration = Duration::from_secs(120); + /// Maximum HTTP message header size in bytes. const MAX_HTTP_MESSAGE_HEADER_SIZE: usize = 8192; @@ -209,25 +215,44 @@ impl HttpClient { #[cfg(not(feature = "tokio"))] let mut reader = std::io::BufReader::new(limited_stream); - macro_rules! read_line { () => { { - let mut line = String::new(); - #[cfg(feature = "tokio")] - let bytes_read = reader.read_line(&mut line).await?; - #[cfg(not(feature = "tokio"))] - let bytes_read = reader.read_line(&mut line)?; - - match bytes_read { - 0 => None, - _ => { - // Remove trailing CRLF - if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { line.pop(); } } - Some(line) - }, - } - } } } + macro_rules! read_line { + () => { read_line!(0) }; + ($retry_count: expr) => { { + let mut line = String::new(); + let mut timeout_count: u64 = 0; + let bytes_read = loop { + #[cfg(feature = "tokio")] + let read_res = reader.read_line(&mut line).await; + #[cfg(not(feature = "tokio"))] + let read_res = reader.read_line(&mut line); + match read_res { + Ok(bytes_read) => break bytes_read, + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + timeout_count += 1; + if timeout_count > $retry_count { + return Err(e); + } else { + continue; + } + } + Err(e) => return Err(e), + } + }; + + match bytes_read { + 0 => None, + _ => { + // Remove trailing CRLF + if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { line.pop(); } } + Some(line) + }, + } + } } + } // Read and parse status line - let status_line = read_line!() + // Note that we allow retrying a few times to reach TCP_STREAM_RESPONSE_TIMEOUT. + let status_line = read_line!(TCP_STREAM_RESPONSE_TIMEOUT.as_secs() / TCP_STREAM_TIMEOUT.as_secs()) .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "no status line"))?; let status = HttpStatus::parse(&status_line)?; -- 2.30.2