Skip to content

Commit 5f448d5

Browse files
committed
Auto merge of #6285 - alexcrichton:more-timeouts, r=ehuss
Timeout batch downloads, not each download This commit switches the timeout logic implemented in #6130 to timeout an entire batch of downloads instead of each download individually. Previously if *any* pending download didn't receive data in 30s we would time out, or if *any* pending download didn't receive 10 bytes in 30s we would time out. On very slow network connections this is highly likely to happen as a trickle of incoming bytes may not be spread equally amongst all connections, and not all connections may actually be active at any one point in time. The fix is to instead apply timeout logic for an entire batch of downloads. Only if zero total data isn't received in the timeout window do we time out. Or in other words, if any data for any download is receive we consider it as not being timed out. Similarly any progress on any download counts as progress towards our speed limit. Closes #6284
2 parents eeb3e16 + 4e1e3f7 commit 5f448d5

File tree

1 file changed

+40
-41
lines changed

1 file changed

+40
-41
lines changed

src/cargo/core/package.rs

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ pub struct Downloads<'a, 'cfg: 'a> {
267267
largest: (u64, String),
268268
start: Instant,
269269
success: bool,
270+
271+
/// Timeout management, both of timeout thresholds as well as whether or not
272+
/// our connection has timed out (and accompanying message if it has).
273+
///
274+
/// Note that timeout management is done manually here instead of in libcurl
275+
/// because we want to apply timeouts to an entire batch of operations, not
276+
/// any one particular single operatino
277+
timeout: ops::HttpTimeout, // timeout configuration
278+
updated_at: Cell<Instant>, // last time we received bytes
279+
next_speed_check: Cell<Instant>, // if threshold isn't 0 by this time, error
280+
next_speed_check_bytes_threshold: Cell<u64>, // decremented when we receive bytes
270281
}
271282

272283
struct Download<'cfg> {
@@ -293,24 +304,7 @@ struct Download<'cfg> {
293304

294305
/// The moment we started this transfer at
295306
start: Instant,
296-
297-
/// Last time we noticed that we got some more data from libcurl
298-
updated_at: Cell<Instant>,
299-
300-
/// Timeout management, both of timeout thresholds as well as whether or not
301-
/// our connection has timed out (and accompanying message if it has).
302-
///
303-
/// Note that timeout management is done manually here because we have a
304-
/// `Multi` with a lot of active transfers but between transfers finishing
305-
/// we perform some possibly slow synchronous work (like grabbing file
306-
/// locks, extracting tarballs, etc). The default timers on our `Multi` keep
307-
/// running during this work, but we don't want them to count towards timing
308-
/// everythig out. As a result, we manage this manually and take the time
309-
/// for synchronous work into account manually.
310-
timeout: ops::HttpTimeout,
311307
timed_out: Cell<Option<String>>,
312-
next_speed_check: Cell<Instant>,
313-
next_speed_check_bytes_threshold: Cell<u64>,
314308

315309
/// Logic used to track retrying this download if it's a spurious failure.
316310
retry: Retry<'cfg>,
@@ -359,6 +353,7 @@ impl<'cfg> PackageSet<'cfg> {
359353

360354
pub fn enable_download<'a>(&'a self) -> CargoResult<Downloads<'a, 'cfg>> {
361355
assert!(!self.downloading.replace(true));
356+
let timeout = ops::HttpTimeout::new(self.config)?;
362357
Ok(Downloads {
363358
start: Instant::now(),
364359
set: self,
@@ -375,6 +370,10 @@ impl<'cfg> PackageSet<'cfg> {
375370
downloaded_bytes: 0,
376371
largest: (0, String::new()),
377372
success: false,
373+
updated_at: Cell::new(Instant::now()),
374+
timeout,
375+
next_speed_check: Cell::new(Instant::now()),
376+
next_speed_check_bytes_threshold: Cell::new(0),
378377
})
379378
}
380379

@@ -446,7 +445,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
446445
debug!("downloading {} as {}", id, token);
447446
assert!(self.pending_ids.insert(id.clone()));
448447

449-
let (mut handle, timeout) = ops::http_handle_and_timeout(self.set.config)?;
448+
let (mut handle, _timeout) = ops::http_handle_and_timeout(self.set.config)?;
450449
handle.get(true)?;
451450
handle.url(&url)?;
452451
handle.follow_location(true)?; // follow redirects
@@ -501,7 +500,6 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
501500
self.set.config.shell().status("Downloading", "crates ...")?;
502501
}
503502

504-
let now = Instant::now();
505503
let dl = Download {
506504
token,
507505
data: RefCell::new(Vec::new()),
@@ -511,11 +509,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
511509
total: Cell::new(0),
512510
current: Cell::new(0),
513511
start: Instant::now(),
514-
updated_at: Cell::new(now),
515-
timeout,
516512
timed_out: Cell::new(None),
517-
next_speed_check: Cell::new(now),
518-
next_speed_check_bytes_threshold: Cell::new(0),
519513
retry: Retry::new(self.set.config)?,
520514
};
521515
self.enqueue(dl, handle)?;
@@ -638,10 +632,8 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
638632
// active downloads to make sure they don't fire because of a slowly
639633
// extracted tarball.
640634
let finish_dur = start.elapsed();
641-
for (dl, _) in self.pending.values_mut() {
642-
dl.updated_at.set(dl.updated_at.get() + finish_dur);
643-
dl.next_speed_check.set(dl.next_speed_check.get() + finish_dur);
644-
}
635+
self.updated_at.set(self.updated_at.get() + finish_dur);
636+
self.next_speed_check.set(self.next_speed_check.get() + finish_dur);
645637

646638
let slot = &self.set.packages[&dl.id];
647639
assert!(slot.fill(pkg).is_ok());
@@ -652,12 +644,12 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
652644
let mut handle = self.set.multi.add(handle)?;
653645
let now = Instant::now();
654646
handle.set_token(dl.token)?;
647+
self.updated_at.set(now);
648+
self.next_speed_check.set(now + self.timeout.dur);
649+
self.next_speed_check_bytes_threshold.set(self.timeout.low_speed_limit as u64);
655650
dl.timed_out.set(None);
656-
dl.updated_at.set(now);
657651
dl.current.set(0);
658652
dl.total.set(0);
659-
dl.next_speed_check.set(now + dl.timeout.dur);
660-
dl.next_speed_check_bytes_threshold.set(dl.timeout.low_speed_limit as u64);
661653
self.pending.insert(dl.token, (dl, handle));
662654
Ok(())
663655
}
@@ -712,25 +704,31 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
712704
dl.total.set(total);
713705
let now = Instant::now();
714706
if cur != dl.current.get() {
707+
let delta = cur - dl.current.get();
708+
let threshold = self.next_speed_check_bytes_threshold.get();
709+
715710
dl.current.set(cur);
716-
dl.updated_at.set(now);
711+
self.updated_at.set(now);
717712

718-
if dl.current.get() >= dl.next_speed_check_bytes_threshold.get() {
719-
dl.next_speed_check.set(now + dl.timeout.dur);
720-
dl.next_speed_check_bytes_threshold.set(
721-
dl.current.get() + dl.timeout.low_speed_limit as u64,
713+
if delta >= threshold {
714+
self.next_speed_check.set(now + self.timeout.dur);
715+
self.next_speed_check_bytes_threshold.set(
716+
self.timeout.low_speed_limit as u64,
722717
);
718+
} else {
719+
self.next_speed_check_bytes_threshold.set(threshold - delta);
723720
}
724721
}
725722
if !self.tick(WhyTick::DownloadUpdate).is_ok() {
726723
return false
727724
}
728725

729726
// If we've spent too long not actually receiving any data we time out.
730-
if now - dl.updated_at.get() > dl.timeout.dur {
727+
if now - self.updated_at.get() > self.timeout.dur {
728+
self.updated_at.set(now);
731729
let msg = format!("failed to download any data for `{}` within {}s",
732730
dl.id,
733-
dl.timeout.dur.as_secs());
731+
self.timeout.dur.as_secs());
734732
dl.timed_out.set(Some(msg));
735733
return false
736734
}
@@ -739,13 +737,14 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
739737
// limit, see if we've transferred enough data during this threshold. If
740738
// it fails this check then we fail because the download is going too
741739
// slowly.
742-
if now >= dl.next_speed_check.get() {
743-
assert!(dl.current.get() < dl.next_speed_check_bytes_threshold.get());
740+
if now >= self.next_speed_check.get() {
741+
self.next_speed_check.set(now + self.timeout.dur);
742+
assert!(self.next_speed_check_bytes_threshold.get() > 0);
744743
let msg = format!("download of `{}` failed to transfer more \
745744
than {} bytes in {}s",
746745
dl.id,
747-
dl.timeout.low_speed_limit,
748-
dl.timeout.dur.as_secs());
746+
self.timeout.low_speed_limit,
747+
self.timeout.dur.as_secs());
749748
dl.timed_out.set(Some(msg));
750749
return false
751750
}

0 commit comments

Comments
 (0)