Skip to content

Commit 796d31e

Browse files
committed
Remove extra GET for tarball uncompressed size
Currently, we make two overlapping GET requests when fetching a tarball: First, the main request to get the headers and the data stream, and then a second request to attempt to fetch the ISIZE field which holds the uncompressed size. Getting the uncompressed size lets us use the stream data after uncompressing to populate our progress bar for tarballs. However, the extra request causes issues for some users, where the second request for the ISIZE field hangs during the TLS handshake. By removing the uncompressed size entirely and connecting the progress bar to the compressed read data, we can avoid the extra request entirely.
1 parent 509c2fc commit 796d31e

File tree

7 files changed

+9
-110
lines changed

7 files changed

+9
-110
lines changed

crates/archive/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ pub enum Origin {
4242

4343
pub trait Archive {
4444
fn compressed_size(&self) -> u64;
45-
fn uncompressed_size(&self) -> Option<u64>;
4645

4746
/// Unpacks the zip archive to the specified destination folder.
4847
fn unpack(

crates/archive/src/tarball.rs

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,20 @@
22
//! tarball in Unix operating systems.
33
44
use std::fs::File;
5-
use std::io::{Read, Seek, SeekFrom};
5+
use std::io::Read;
66
use std::path::Path;
77

88
use super::{Archive, ArchiveError, Origin};
99
use attohttpc::header::HeaderMap;
1010
use flate2::read::GzDecoder;
1111
use fs_utils::ensure_containing_dir_exists;
12-
use headers::{AcceptRanges, ContentLength, Header, HeaderMapExt, Range};
12+
use headers::{ContentLength, Header, HeaderMapExt};
1313
use progress_read::ProgressRead;
1414
use tee::TeeReader;
1515

1616
/// A Node installation tarball.
1717
pub struct Tarball {
1818
compressed_size: u64,
19-
// Some servers don't return the right data for byte range queries, so
20-
// getting the uncompressed archive size for tarballs is an Option.
21-
// If the uncompressed size is not available, the compressed size will be
22-
// used for the download/unpack progress indicator, so that will be slightly off.
23-
uncompressed_size: Option<u64>,
2419
data: Box<dyn Read>,
2520
origin: Origin,
2621
}
@@ -36,11 +31,9 @@ fn content_length(headers: &HeaderMap) -> Result<u64, ArchiveError> {
3631

3732
impl Tarball {
3833
/// Loads a tarball from the specified file.
39-
pub fn load(mut source: File) -> Result<Box<dyn Archive>, ArchiveError> {
40-
let uncompressed_size = load_uncompressed_size(&mut source);
34+
pub fn load(source: File) -> Result<Box<dyn Archive>, ArchiveError> {
4135
let compressed_size = source.metadata()?.len();
4236
Ok(Box::new(Tarball {
43-
uncompressed_size,
4437
compressed_size,
4538
data: Box::new(source),
4639
origin: Origin::Local,
@@ -58,18 +51,12 @@ impl Tarball {
5851
}
5952

6053
let compressed_size = content_length(&headers)?;
61-
let uncompressed_size = if accepts_byte_ranges(&headers) {
62-
fetch_uncompressed_size(url, compressed_size)
63-
} else {
64-
None
65-
};
6654

6755
ensure_containing_dir_exists(&cache_file)?;
6856
let file = File::create(cache_file)?;
6957
let data = Box::new(TeeReader::new(response, file));
7058

7159
Ok(Box::new(Tarball {
72-
uncompressed_size,
7360
compressed_size,
7461
data,
7562
origin: Origin::Remote,
@@ -81,16 +68,13 @@ impl Archive for Tarball {
8168
fn compressed_size(&self) -> u64 {
8269
self.compressed_size
8370
}
84-
fn uncompressed_size(&self) -> Option<u64> {
85-
self.uncompressed_size
86-
}
8771
fn unpack(
8872
self: Box<Self>,
8973
dest: &Path,
9074
progress: &mut dyn FnMut(&(), usize),
9175
) -> Result<(), ArchiveError> {
92-
let decoded = GzDecoder::new(self.data);
93-
let mut tarball = tar::Archive::new(ProgressRead::new(decoded, (), progress));
76+
let decoded = GzDecoder::new(ProgressRead::new(self.data, (), progress));
77+
let mut tarball = tar::Archive::new(decoded);
9478
tarball.unpack(dest)?;
9579
Ok(())
9680
}
@@ -99,78 +83,6 @@ impl Archive for Tarball {
9983
}
10084
}
10185

102-
// From http://www.gzip.org/zlib/rfc-gzip.html#member-format
103-
//
104-
// 0 1 2 3 4 5 6 7
105-
// +---+---+---+---+---+---+---+---+
106-
// | CRC32 | ISIZE |
107-
// +---+---+---+---+---+---+---+---+
108-
//
109-
// ISIZE (Input SIZE)
110-
// This contains the size of the original (uncompressed) input data modulo 2^32.
111-
112-
/// Fetches just the `isize` field (the field that indicates the uncompressed size)
113-
/// of a gzip file from a URL. This makes two round-trips to the server but avoids
114-
/// downloading the entire gzip file. For very small files it's unlikely to be
115-
/// more efficient than simply downloading the entire file up front.
116-
fn fetch_isize(url: &str, len: u64) -> Result<[u8; 4], ArchiveError> {
117-
let (status, headers, mut response) = {
118-
let mut request = attohttpc::get(url);
119-
request
120-
.headers_mut()
121-
.typed_insert(Range::bytes(len - 4..len).unwrap());
122-
request.send()?.split()
123-
};
124-
125-
if !status.is_success() {
126-
return Err(ArchiveError::HttpError(status));
127-
}
128-
129-
let actual_length = content_length(&headers)?;
130-
131-
if actual_length != 4 {
132-
return Err(ArchiveError::UnexpectedContentLengthError(actual_length));
133-
}
134-
135-
let mut buf = [0; 4];
136-
response.read_exact(&mut buf)?;
137-
Ok(buf)
138-
}
139-
140-
/// Loads the `isize` field (the field that indicates the uncompressed size)
141-
/// of a gzip file from disk.
142-
fn load_isize(file: &mut File) -> Result<[u8; 4], ArchiveError> {
143-
file.seek(SeekFrom::End(-4))?;
144-
let mut buf = [0; 4];
145-
file.read_exact(&mut buf)?;
146-
file.seek(SeekFrom::Start(0))?;
147-
Ok(buf)
148-
}
149-
150-
fn accepts_byte_ranges(headers: &HeaderMap) -> bool {
151-
headers
152-
.typed_get::<AcceptRanges>()
153-
.is_some_and(|v| v == AcceptRanges::bytes())
154-
}
155-
156-
/// Determines the uncompressed size of a gzip file hosted at the specified
157-
/// URL by fetching just the metadata associated with the file. This makes
158-
/// an extra round-trip to the server, so it's only more efficient than just
159-
/// downloading the file if the file is large enough that downloading it is
160-
/// slower than the extra round trips.
161-
fn fetch_uncompressed_size(url: &str, len: u64) -> Option<u64> {
162-
// if there is an error, we ignore it and return None, instead of failing
163-
fetch_isize(url, len)
164-
.ok()
165-
.map(|s| u32::from_le_bytes(s) as u64)
166-
}
167-
168-
/// Determines the uncompressed size of the specified gzip file on disk.
169-
fn load_uncompressed_size(file: &mut File) -> Option<u64> {
170-
// if there is an error, we ignore it and return None, instead of failing
171-
load_isize(file).ok().map(|s| u32::from_le_bytes(s) as u64)
172-
}
173-
17486
#[cfg(test)]
17587
pub mod tests {
17688

@@ -192,7 +104,6 @@ pub mod tests {
192104
let test_file = File::open(test_file_path).expect("Couldn't open test file");
193105
let tarball = Tarball::load(test_file).expect("Failed to load tarball");
194106

195-
assert_eq!(tarball.uncompressed_size(), Some(10240));
196107
assert_eq!(tarball.compressed_size(), 402);
197108
}
198109
}

crates/archive/src/zip.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ impl Archive for Zip {
6060
fn compressed_size(&self) -> u64 {
6161
self.compressed_size
6262
}
63-
fn uncompressed_size(&self) -> Option<u64> {
64-
None
65-
}
6663
fn unpack(
6764
self: Box<Self>,
6865
dest: &Path,

crates/volta-core/src/tool/node/fetch.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ fn unpack_archive(archive: Box<dyn Archive>, version: &Version) -> Fallible<Node
9595
let progress = progress_bar(
9696
archive.origin(),
9797
&tool_version("node", version),
98-
archive
99-
.uncompressed_size()
100-
.unwrap_or_else(|| archive.compressed_size()),
98+
archive.compressed_size(),
10199
);
102100
let version_string = version.to_string();
103101

crates/volta-core/src/tool/npm/fetch.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ fn unpack_archive(archive: Box<dyn Archive>, version: &Version) -> Fallible<()>
6262
let progress = progress_bar(
6363
archive.origin(),
6464
&tool_version("npm", version),
65-
archive
66-
.uncompressed_size()
67-
.unwrap_or_else(|| archive.compressed_size()),
65+
archive.compressed_size(),
6866
);
6967
let version_string = version.to_string();
7068

crates/volta-core/src/tool/pnpm/fetch.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ fn unpack_archive(archive: Box<dyn Archive>, version: &Version) -> Fallible<()>
6464
let progress = progress_bar(
6565
archive.origin(),
6666
&tool_version("pnpm", version),
67-
archive
68-
.uncompressed_size()
69-
.unwrap_or_else(|| archive.compressed_size()),
67+
archive.compressed_size(),
7068
);
7169
let version_string = version.to_string();
7270

crates/volta-core/src/tool/yarn/fetch.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ fn unpack_archive(archive: Box<dyn Archive>, version: &Version) -> Fallible<()>
6666
let progress = progress_bar(
6767
archive.origin(),
6868
&tool_version("yarn", version),
69-
archive
70-
.uncompressed_size()
71-
.unwrap_or_else(|| archive.compressed_size()),
69+
archive.compressed_size(),
7270
);
7371
let version_string = version.to_string();
7472

0 commit comments

Comments
 (0)