Skip to content

Commit b4cf8eb

Browse files
authored
fix: add fetch timeout and TCP keepalive to HTTP client (#694)
1 parent 5abd7d3 commit b4cf8eb

2 files changed

Lines changed: 59 additions & 7 deletions

File tree

deno/http_util.rs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,23 @@ use http_body_util::BodyExt;
2727

2828
use std::collections::HashMap;
2929
use std::sync::Arc;
30+
use std::sync::OnceLock;
3031
use std::thread::ThreadId;
3132
use std::time::Duration;
3233
use std::time::SystemTime;
3334
use thiserror::Error;
3435

36+
static FETCH_TIMEOUT: OnceLock<Option<Duration>> = OnceLock::new();
37+
38+
fn fetch_timeout() -> Option<Duration> {
39+
*FETCH_TIMEOUT.get_or_init(|| {
40+
std::env::var("DENO_FETCH_TIMEOUT_SECS")
41+
.ok()
42+
.and_then(|v| v.parse::<u64>().ok())
43+
.map(Duration::from_secs)
44+
})
45+
}
46+
3547
// TODO(ry) HTTP headers are not unique key, value pairs. There may be more than
3648
// one header line with the same key. This should be changed to something like
3749
// Vec<(String, String)>
@@ -391,13 +403,27 @@ impl HttpClient {
391403
let accepts_val = HeaderValue::from_str(&accept)?;
392404
request.headers_mut().insert(ACCEPT, accepts_val);
393405
}
394-
let response = match self.client.clone().send(request).await {
395-
Ok(resp) => resp,
396-
Err(err) => {
397-
if err.is_connect_error() {
398-
return Ok(FetchOnceResult::RequestError(err.to_string()));
406+
let response = {
407+
let send_fut = self.client.clone().send(request);
408+
let resp = if let Some(dur) = fetch_timeout() {
409+
tokio::time::timeout(dur, send_fut).await.map_err(|_| {
410+
anyhow::anyhow!(
411+
"Fetch '{}' timed out after {}s",
412+
args.url,
413+
dur.as_secs()
414+
)
415+
})?
416+
} else {
417+
send_fut.await
418+
};
419+
match resp {
420+
Ok(r) => r,
421+
Err(err) => {
422+
if err.is_connect_error() {
423+
return Ok(FetchOnceResult::RequestError(err.to_string()));
424+
}
425+
return Err(err.into());
399426
}
400-
return Err(err.into());
401427
}
402428
};
403429

@@ -450,7 +476,19 @@ impl HttpClient {
450476
return Err(err);
451477
}
452478

453-
let body = get_response_body_with_progress(response).await?;
479+
let body = if let Some(dur) = fetch_timeout() {
480+
tokio::time::timeout(dur, get_response_body_with_progress(response))
481+
.await
482+
.map_err(|_| {
483+
anyhow::anyhow!(
484+
"Fetch '{}' body timed out after {}s",
485+
args.url,
486+
dur.as_secs()
487+
)
488+
})??
489+
} else {
490+
get_response_body_with_progress(response).await?
491+
};
454492

455493
Ok(FetchOnceResult::Code(body, result_headers))
456494
}

vendor/deno_fetch/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,22 @@ use std::path::PathBuf;
1515
use std::pin::Pin;
1616
use std::rc::Rc;
1717
use std::sync::Arc;
18+
use std::sync::OnceLock;
1819
use std::task::Context;
1920
use std::task::Poll;
2021

22+
static TCP_KEEPALIVE: OnceLock<std::time::Duration> = OnceLock::new();
23+
24+
fn tcp_keepalive() -> std::time::Duration {
25+
*TCP_KEEPALIVE.get_or_init(|| {
26+
let secs = std::env::var("DENO_TCP_KEEPALIVE_SECS")
27+
.ok()
28+
.and_then(|v| v.parse::<u64>().ok())
29+
.unwrap_or(30);
30+
std::time::Duration::from_secs(secs)
31+
})
32+
}
33+
2134
use deno_core::futures::stream::Peekable;
2235
use deno_core::futures::Future;
2336
use deno_core::futures::FutureExt;
@@ -1016,6 +1029,7 @@ pub fn create_http_client(
10161029
let mut http_connector =
10171030
HttpConnector::new_with_resolver(options.dns_resolver.clone());
10181031
http_connector.enforce_http(false);
1032+
http_connector.set_keepalive(Some(tcp_keepalive()));
10191033

10201034
let user_agent = user_agent.parse::<HeaderValue>().map_err(|_| {
10211035
HttpClientCreateError::InvalidUserAgent(user_agent.to_string())

0 commit comments

Comments
 (0)