From ae08838084afbaffb2e69969a6c65d7d064e20e6 Mon Sep 17 00:00:00 2001 From: Nyannyacha Date: Tue, 21 Apr 2026 11:26:25 +0000 Subject: [PATCH] fix: add fetch timeout and TCP keepalive to HTTP client --- deno/http_util.rs | 52 ++++++++++++++++++++++++++++++++++------ vendor/deno_fetch/lib.rs | 14 +++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/deno/http_util.rs b/deno/http_util.rs index 426f6ce28..cd46dfc2d 100644 --- a/deno/http_util.rs +++ b/deno/http_util.rs @@ -27,11 +27,23 @@ use http_body_util::BodyExt; use std::collections::HashMap; use std::sync::Arc; +use std::sync::OnceLock; use std::thread::ThreadId; use std::time::Duration; use std::time::SystemTime; use thiserror::Error; +static FETCH_TIMEOUT: OnceLock> = OnceLock::new(); + +fn fetch_timeout() -> Option { + *FETCH_TIMEOUT.get_or_init(|| { + std::env::var("DENO_FETCH_TIMEOUT_SECS") + .ok() + .and_then(|v| v.parse::().ok()) + .map(Duration::from_secs) + }) +} + // TODO(ry) HTTP headers are not unique key, value pairs. There may be more than // one header line with the same key. This should be changed to something like // Vec<(String, String)> @@ -391,13 +403,27 @@ impl HttpClient { let accepts_val = HeaderValue::from_str(&accept)?; request.headers_mut().insert(ACCEPT, accepts_val); } - let response = match self.client.clone().send(request).await { - Ok(resp) => resp, - Err(err) => { - if err.is_connect_error() { - return Ok(FetchOnceResult::RequestError(err.to_string())); + let response = { + let send_fut = self.client.clone().send(request); + let resp = if let Some(dur) = fetch_timeout() { + tokio::time::timeout(dur, send_fut).await.map_err(|_| { + anyhow::anyhow!( + "Fetch '{}' timed out after {}s", + args.url, + dur.as_secs() + ) + })? + } else { + send_fut.await + }; + match resp { + Ok(r) => r, + Err(err) => { + if err.is_connect_error() { + return Ok(FetchOnceResult::RequestError(err.to_string())); + } + return Err(err.into()); } - return Err(err.into()); } }; @@ -450,7 +476,19 @@ impl HttpClient { return Err(err); } - let body = get_response_body_with_progress(response).await?; + let body = if let Some(dur) = fetch_timeout() { + tokio::time::timeout(dur, get_response_body_with_progress(response)) + .await + .map_err(|_| { + anyhow::anyhow!( + "Fetch '{}' body timed out after {}s", + args.url, + dur.as_secs() + ) + })?? + } else { + get_response_body_with_progress(response).await? + }; Ok(FetchOnceResult::Code(body, result_headers)) } diff --git a/vendor/deno_fetch/lib.rs b/vendor/deno_fetch/lib.rs index a3f5d03e6..38e79cd2e 100644 --- a/vendor/deno_fetch/lib.rs +++ b/vendor/deno_fetch/lib.rs @@ -15,9 +15,22 @@ use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; +use std::sync::OnceLock; use std::task::Context; use std::task::Poll; +static TCP_KEEPALIVE: OnceLock = OnceLock::new(); + +fn tcp_keepalive() -> std::time::Duration { + *TCP_KEEPALIVE.get_or_init(|| { + let secs = std::env::var("DENO_TCP_KEEPALIVE_SECS") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(30); + std::time::Duration::from_secs(secs) + }) +} + use deno_core::futures::stream::Peekable; use deno_core::futures::Future; use deno_core::futures::FutureExt; @@ -1016,6 +1029,7 @@ pub fn create_http_client( let mut http_connector = HttpConnector::new_with_resolver(options.dns_resolver.clone()); http_connector.enforce_http(false); + http_connector.set_keepalive(Some(tcp_keepalive())); let user_agent = user_agent.parse::().map_err(|_| { HttpClientCreateError::InvalidUserAgent(user_agent.to_string())