Skip to content

Commit f22b590

Browse files
fix(download_timeout): introduce RUSTUP_DOWNLOAD_TIMEOUT for overriding download timeout
As some (corporate) firewalls take too much time to verify the downloads, the timeout is reached and the user cannot complete its installation. Besides changing the timeout from 30 secs to 3 mins, a new environment variable is introduced to allow users to override the timeout value to their taste.
1 parent 7a9e227 commit f22b590

File tree

3 files changed

+64
-30
lines changed

3 files changed

+64
-30
lines changed

doc/user-guide/src/environment-variables.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666

6767
- `RUSTUP_TERM_WIDTH` (default: none). Allows to override the terminal width for progress bars.
6868

69+
- `RUSTUP_DOWNLOAD_TIMEOUT` *unstable* (default: 180). Allows to override the default
70+
timeout (in seconds) for downloading components.
71+
6972
[directive syntax]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
7073
[dc]: https://docs.docker.com/storage/storagedriver/overlayfs-driver/#modifying-files-or-directories
7174
[override]: overrides.md

src/download/mod.rs

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! Easy file downloading
22
33
use std::fs::remove_file;
4+
use std::num::NonZeroU64;
45
use std::path::Path;
6+
use std::str::FromStr;
7+
use std::time::Duration;
58

69
use anyhow::Context;
710
#[cfg(any(
@@ -194,6 +197,13 @@ async fn download_file_(
194197
_ => Backend::Curl,
195198
};
196199

200+
let timeout = Duration::from_secs(match process.var("RUSTUP_DOWNLOAD_TIMEOUT") {
201+
Ok(s) => NonZeroU64::from_str(&s).context(
202+
"invalid value in RUSTUP_DOWNLOAD_TIMEOUT -- must be a natural number greater than zero",
203+
)?.get(),
204+
Err(_) => 180,
205+
});
206+
197207
notify_handler(match backend {
198208
#[cfg(feature = "curl-backend")]
199209
Backend::Curl => Notification::UsingCurl,
@@ -202,7 +212,7 @@ async fn download_file_(
202212
});
203213

204214
let res = backend
205-
.download_to_path(url, path, resume_from_partial, Some(callback))
215+
.download_to_path(url, path, resume_from_partial, Some(callback), timeout)
206216
.await;
207217

208218
notify_handler(Notification::DownloadFinished);
@@ -241,9 +251,10 @@ impl Backend {
241251
path: &Path,
242252
resume_from_partial: bool,
243253
callback: Option<DownloadCallback<'_>>,
254+
timeout: Duration,
244255
) -> anyhow::Result<()> {
245256
let Err(err) = self
246-
.download_impl(url, path, resume_from_partial, callback)
257+
.download_impl(url, path, resume_from_partial, callback, timeout)
247258
.await
248259
else {
249260
return Ok(());
@@ -265,6 +276,7 @@ impl Backend {
265276
path: &Path,
266277
resume_from_partial: bool,
267278
callback: Option<DownloadCallback<'_>>,
279+
timeout: Duration,
268280
) -> anyhow::Result<()> {
269281
use std::cell::RefCell;
270282
use std::fs::OpenOptions;
@@ -324,7 +336,7 @@ impl Backend {
324336
let file = RefCell::new(file);
325337

326338
// TODO: the sync callback will stall the async runtime if IO calls block, which is OS dependent. Rearrange.
327-
self.download(url, resume_from, &|event| {
339+
self.download(url, resume_from, timeout, &|event| {
328340
if let Event::DownloadDataReceived(data) = event {
329341
file.borrow_mut()
330342
.write_all(data)
@@ -356,13 +368,14 @@ impl Backend {
356368
self,
357369
url: &Url,
358370
resume_from: u64,
371+
timeout: Duration,
359372
callback: DownloadCallback<'_>,
360373
) -> anyhow::Result<()> {
361374
match self {
362375
#[cfg(feature = "curl-backend")]
363-
Self::Curl => curl::download(url, resume_from, callback),
376+
Self::Curl => curl::download(url, resume_from, callback, timeout),
364377
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
365-
Self::Reqwest(tls) => tls.download(url, resume_from, callback).await,
378+
Self::Reqwest(tls) => tls.download(url, resume_from, callback, timeout).await,
366379
}
367380
}
368381
}
@@ -383,12 +396,13 @@ impl TlsBackend {
383396
url: &Url,
384397
resume_from: u64,
385398
callback: DownloadCallback<'_>,
399+
timeout: Duration,
386400
) -> anyhow::Result<()> {
387401
let client = match self {
388402
#[cfg(feature = "reqwest-rustls-tls")]
389-
Self::Rustls => reqwest_be::rustls_client()?,
403+
Self::Rustls => reqwest_be::rustls_client(timeout)?,
390404
#[cfg(feature = "reqwest-native-tls")]
391-
Self::NativeTls => &reqwest_be::CLIENT_NATIVE_TLS,
405+
Self::NativeTls => reqwest_be::native_tls_client(timeout)?,
392406
};
393407

394408
reqwest_be::download(url, resume_from, callback, client).await
@@ -424,6 +438,7 @@ mod curl {
424438
url: &Url,
425439
resume_from: u64,
426440
callback: &dyn Fn(Event<'_>) -> Result<()>,
441+
timeout: Duration,
427442
) -> Result<()> {
428443
// Fetch either a cached libcurl handle (which will preserve open
429444
// connections) or create a new one if it isn't listed.
@@ -446,8 +461,8 @@ mod curl {
446461
let _ = handle.resume_from(0);
447462
}
448463

449-
// Take at most 30s to connect
450-
handle.connect_timeout(Duration::new(30, 0))?;
464+
// Take at most 3m to connect if the `RUSTUP_DOWNLOAD_TIMEOUT` env var is not set.
465+
handle.connect_timeout(timeout)?;
451466

452467
{
453468
let cberr = RefCell::new(None);
@@ -526,9 +541,7 @@ mod curl {
526541
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
527542
mod reqwest_be {
528543
use std::io;
529-
#[cfg(feature = "reqwest-native-tls")]
530-
use std::sync::LazyLock;
531-
#[cfg(feature = "reqwest-rustls-tls")]
544+
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
532545
use std::sync::{Arc, OnceLock};
533546
use std::time::Duration;
534547

@@ -586,11 +599,10 @@ mod reqwest_be {
586599
.pool_max_idle_per_host(0)
587600
.gzip(false)
588601
.proxy(Proxy::custom(env_proxy))
589-
.read_timeout(Duration::from_secs(30))
590602
}
591603

592604
#[cfg(feature = "reqwest-rustls-tls")]
593-
pub(super) fn rustls_client() -> Result<&'static Client, DownloadError> {
605+
pub(super) fn rustls_client(timeout: Duration) -> Result<&'static Client, DownloadError> {
594606
if let Some(client) = CLIENT_RUSTLS_TLS.get() {
595607
return Ok(client);
596608
}
@@ -607,6 +619,7 @@ mod reqwest_be {
607619
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
608620

609621
let client = client_generic()
622+
.read_timeout(timeout)
610623
.use_preconfigured_tls(tls_config)
611624
.user_agent(super::REQWEST_RUSTLS_TLS_USER_AGENT)
612625
.build()
@@ -622,21 +635,24 @@ mod reqwest_be {
622635
static CLIENT_RUSTLS_TLS: OnceLock<Client> = OnceLock::new();
623636

624637
#[cfg(feature = "reqwest-native-tls")]
625-
pub(super) static CLIENT_NATIVE_TLS: LazyLock<Client> = LazyLock::new(|| {
626-
let catcher = || {
627-
client_generic()
628-
.user_agent(super::REQWEST_DEFAULT_TLS_USER_AGENT)
629-
.build()
630-
};
638+
pub(super) fn native_tls_client(timeout: Duration) -> Result<&'static Client, DownloadError> {
639+
if let Some(client) = CLIENT_NATIVE_TLS.get() {
640+
return Ok(client);
641+
}
631642

632-
// woah, an unwrap?!
633-
// It's OK. This is the same as what is happening in curl.
634-
//
635-
// The curl::Easy::new() internally assert!s that the initialized
636-
// Easy is not null. Inside reqwest, the errors here would be from
637-
// the TLS library returning a null pointer as well.
638-
catcher().unwrap()
639-
});
643+
let client = client_generic()
644+
.read_timeout(timeout)
645+
.user_agent(super::REQWEST_DEFAULT_TLS_USER_AGENT)
646+
.build()
647+
.map_err(DownloadError::Reqwest)?;
648+
649+
let _ = CLIENT_NATIVE_TLS.set(client);
650+
651+
Ok(CLIENT_NATIVE_TLS.get().unwrap())
652+
}
653+
654+
#[cfg(feature = "reqwest-native-tls")]
655+
static CLIENT_NATIVE_TLS: OnceLock<Client> = OnceLock::new();
640656

641657
fn env_proxy(url: &Url) -> Option<Url> {
642658
env_proxy::for_url(url).to_url()

src/download/tests.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use tempfile::TempDir;
1919
mod curl {
2020
use std::sync::Mutex;
2121
use std::sync::atomic::{AtomicBool, Ordering};
22+
use std::time::Duration;
2223

2324
use url::Url;
2425

@@ -36,7 +37,13 @@ mod curl {
3637

3738
let from_url = Url::from_file_path(&from_path).unwrap();
3839
Backend::Curl
39-
.download_to_path(&from_url, &target_path, true, None)
40+
.download_to_path(
41+
&from_url,
42+
&target_path,
43+
true,
44+
None,
45+
Duration::from_secs(180),
46+
)
4047
.await
4148
.expect("Test download failed");
4249

@@ -83,6 +90,7 @@ mod curl {
8390

8491
Ok(())
8592
}),
93+
Duration::from_secs(180),
8694
)
8795
.await
8896
.expect("Test download failed");
@@ -185,7 +193,13 @@ mod reqwest {
185193

186194
let from_url = Url::from_file_path(&from_path).unwrap();
187195
Backend::Reqwest(TlsBackend::NativeTls)
188-
.download_to_path(&from_url, &target_path, true, None)
196+
.download_to_path(
197+
&from_url,
198+
&target_path,
199+
true,
200+
None,
201+
Duration::from_secs(180),
202+
)
189203
.await
190204
.expect("Test download failed");
191205

@@ -232,6 +246,7 @@ mod reqwest {
232246

233247
Ok(())
234248
}),
249+
Duration::from_secs(180),
235250
)
236251
.await
237252
.expect("Test download failed");

0 commit comments

Comments
 (0)