Skip to content

Commit d19ffb2

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 d19ffb2

File tree

3 files changed

+63
-30
lines changed

3 files changed

+63
-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` (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: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Easy file downloading
22
33
use std::fs::remove_file;
4+
use std::num::NonZeroU64;
45
use std::path::Path;
6+
use std::time::Duration;
57

68
use anyhow::Context;
79
#[cfg(any(
@@ -194,6 +196,13 @@ async fn download_file_(
194196
_ => Backend::Curl,
195197
};
196198

199+
let timeout = Duration::from_secs(match process.var("RUSTUP_DOWNLOAD_TIMEOUT") {
200+
Ok(s) => s.parse::<NonZeroU64>().context(
201+
"invalid value in RUSTUP_DOWNLOAD_TIMEOUT -- must be a natural number greater than zero",
202+
)?.get(),
203+
Err(_) => 180,
204+
});
205+
197206
notify_handler(match backend {
198207
#[cfg(feature = "curl-backend")]
199208
Backend::Curl => Notification::UsingCurl,
@@ -202,7 +211,7 @@ async fn download_file_(
202211
});
203212

204213
let res = backend
205-
.download_to_path(url, path, resume_from_partial, Some(callback))
214+
.download_to_path(url, path, resume_from_partial, Some(callback), timeout)
206215
.await;
207216

208217
notify_handler(Notification::DownloadFinished);
@@ -241,9 +250,10 @@ impl Backend {
241250
path: &Path,
242251
resume_from_partial: bool,
243252
callback: Option<DownloadCallback<'_>>,
253+
timeout: Duration,
244254
) -> anyhow::Result<()> {
245255
let Err(err) = self
246-
.download_impl(url, path, resume_from_partial, callback)
256+
.download_impl(url, path, resume_from_partial, callback, timeout)
247257
.await
248258
else {
249259
return Ok(());
@@ -265,6 +275,7 @@ impl Backend {
265275
path: &Path,
266276
resume_from_partial: bool,
267277
callback: Option<DownloadCallback<'_>>,
278+
timeout: Duration,
268279
) -> anyhow::Result<()> {
269280
use std::cell::RefCell;
270281
use std::fs::OpenOptions;
@@ -324,7 +335,7 @@ impl Backend {
324335
let file = RefCell::new(file);
325336

326337
// 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| {
338+
self.download(url, resume_from, timeout, &|event| {
328339
if let Event::DownloadDataReceived(data) = event {
329340
file.borrow_mut()
330341
.write_all(data)
@@ -356,13 +367,14 @@ impl Backend {
356367
self,
357368
url: &Url,
358369
resume_from: u64,
370+
timeout: Duration,
359371
callback: DownloadCallback<'_>,
360372
) -> anyhow::Result<()> {
361373
match self {
362374
#[cfg(feature = "curl-backend")]
363-
Self::Curl => curl::download(url, resume_from, callback),
375+
Self::Curl => curl::download(url, resume_from, callback, timeout),
364376
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
365-
Self::Reqwest(tls) => tls.download(url, resume_from, callback).await,
377+
Self::Reqwest(tls) => tls.download(url, resume_from, callback, timeout).await,
366378
}
367379
}
368380
}
@@ -383,12 +395,13 @@ impl TlsBackend {
383395
url: &Url,
384396
resume_from: u64,
385397
callback: DownloadCallback<'_>,
398+
timeout: Duration,
386399
) -> anyhow::Result<()> {
387400
let client = match self {
388401
#[cfg(feature = "reqwest-rustls-tls")]
389-
Self::Rustls => reqwest_be::rustls_client()?,
402+
Self::Rustls => reqwest_be::rustls_client(timeout)?,
390403
#[cfg(feature = "reqwest-native-tls")]
391-
Self::NativeTls => &reqwest_be::CLIENT_NATIVE_TLS,
404+
Self::NativeTls => reqwest_be::native_tls_client(timeout)?,
392405
};
393406

394407
reqwest_be::download(url, resume_from, callback, client).await
@@ -424,6 +437,7 @@ mod curl {
424437
url: &Url,
425438
resume_from: u64,
426439
callback: &dyn Fn(Event<'_>) -> Result<()>,
440+
timeout: Duration,
427441
) -> Result<()> {
428442
// Fetch either a cached libcurl handle (which will preserve open
429443
// connections) or create a new one if it isn't listed.
@@ -446,8 +460,8 @@ mod curl {
446460
let _ = handle.resume_from(0);
447461
}
448462

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

452466
{
453467
let cberr = RefCell::new(None);
@@ -526,9 +540,7 @@ mod curl {
526540
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
527541
mod reqwest_be {
528542
use std::io;
529-
#[cfg(feature = "reqwest-native-tls")]
530-
use std::sync::LazyLock;
531-
#[cfg(feature = "reqwest-rustls-tls")]
543+
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
532544
use std::sync::{Arc, OnceLock};
533545
use std::time::Duration;
534546

@@ -586,11 +598,10 @@ mod reqwest_be {
586598
.pool_max_idle_per_host(0)
587599
.gzip(false)
588600
.proxy(Proxy::custom(env_proxy))
589-
.read_timeout(Duration::from_secs(30))
590601
}
591602

592603
#[cfg(feature = "reqwest-rustls-tls")]
593-
pub(super) fn rustls_client() -> Result<&'static Client, DownloadError> {
604+
pub(super) fn rustls_client(timeout: Duration) -> Result<&'static Client, DownloadError> {
594605
if let Some(client) = CLIENT_RUSTLS_TLS.get() {
595606
return Ok(client);
596607
}
@@ -607,6 +618,7 @@ mod reqwest_be {
607618
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
608619

609620
let client = client_generic()
621+
.read_timeout(timeout)
610622
.use_preconfigured_tls(tls_config)
611623
.user_agent(super::REQWEST_RUSTLS_TLS_USER_AGENT)
612624
.build()
@@ -622,21 +634,24 @@ mod reqwest_be {
622634
static CLIENT_RUSTLS_TLS: OnceLock<Client> = OnceLock::new();
623635

624636
#[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-
};
637+
pub(super) fn native_tls_client(timeout: Duration) -> Result<&'static Client, DownloadError> {
638+
if let Some(client) = CLIENT_NATIVE_TLS.get() {
639+
return Ok(client);
640+
}
631641

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-
});
642+
let client = client_generic()
643+
.read_timeout(timeout)
644+
.user_agent(super::REQWEST_DEFAULT_TLS_USER_AGENT)
645+
.build()
646+
.map_err(DownloadError::Reqwest)?;
647+
648+
let _ = CLIENT_NATIVE_TLS.set(client);
649+
650+
Ok(CLIENT_NATIVE_TLS.get().unwrap())
651+
}
652+
653+
#[cfg(feature = "reqwest-native-tls")]
654+
static CLIENT_NATIVE_TLS: OnceLock<Client> = OnceLock::new();
640655

641656
fn env_proxy(url: &Url) -> Option<Url> {
642657
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)