Skip to content

Commit 5580890

Browse files
authored
Merge pull request #1850 from rbtcollins/new-tar-rs
Avoid blocking on CloseHandle
2 parents d78ce04 + ff0dc0d commit 5580890

File tree

7 files changed

+212
-33
lines changed

7 files changed

+212
-33
lines changed

Cargo.lock

+17-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ scopeguard = "1"
4343
semver = "0.9"
4444
sha2 = "0.8"
4545
strsim = "0.9.1"
46-
tar = "0.4"
46+
tar = "0.4.26"
4747
tempdir = "0.3.4"
4848
# FIXME(issue #1788)
4949
term = "=0.5.1"
50+
threadpool = "1"
5051
time = "0.1.34"
5152
toml = "0.5"
5253
url = "1"

src/cli/download_tracker.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub struct DownloadTracker {
4242
/// If we have displayed progress, this is the number of characters we
4343
/// rendered, so we can erase it cleanly.
4444
displayed_charcount: Option<usize>,
45+
/// What units to show progress in
46+
units: Vec<String>,
4547
}
4648

4749
impl DownloadTracker {
@@ -56,6 +58,7 @@ impl DownloadTracker {
5658
last_sec: None,
5759
term: term2::stdout(),
5860
displayed_charcount: None,
61+
units: vec!["B".into(); 1],
5962
}
6063
}
6164

@@ -76,6 +79,15 @@ impl DownloadTracker {
7679
self.download_finished();
7780
true
7881
}
82+
Notification::Install(In::Utils(Un::DownloadPushUnits(units))) => {
83+
self.push_units(units.into());
84+
true
85+
}
86+
Notification::Install(In::Utils(Un::DownloadPopUnits)) => {
87+
self.pop_units();
88+
true
89+
}
90+
7991
_ => false,
8092
}
8193
}
@@ -139,11 +151,13 @@ impl DownloadTracker {
139151
}
140152
/// Display the tracked download information to the terminal.
141153
fn display(&mut self) {
142-
let total_h = Size(self.total_downloaded);
154+
// Panic if someone pops the default bytes unit...
155+
let units = &self.units.last().unwrap();
156+
let total_h = Size(self.total_downloaded, units);
143157
let sum = self.downloaded_last_few_secs.iter().fold(0, |a, &v| a + v);
144158
let len = self.downloaded_last_few_secs.len();
145159
let speed = if len > 0 { sum / len } else { 0 };
146-
let speed_h = Size(speed);
160+
let speed_h = Size(speed, units);
147161
let elapsed_h = Duration(precise_time_s() - self.start_sec);
148162

149163
// First, move to the start of the current line and clear it.
@@ -163,7 +177,7 @@ impl DownloadTracker {
163177

164178
let output = match self.content_len {
165179
Some(content_len) => {
166-
let content_len_h = Size(content_len);
180+
let content_len_h = Size(content_len, units);
167181
let content_len = content_len as f64;
168182
let percent = (self.total_downloaded as f64 / content_len) * 100.;
169183
let remaining = content_len - self.total_downloaded as f64;
@@ -184,6 +198,14 @@ impl DownloadTracker {
184198
let _ = self.term.flush();
185199
self.displayed_charcount = Some(output.chars().count());
186200
}
201+
202+
pub fn push_units(&mut self, new_units: String) {
203+
self.units.push(new_units);
204+
}
205+
206+
pub fn pop_units(&mut self) {
207+
self.units.pop();
208+
}
187209
}
188210

189211
/// Human readable representation of duration(seconds).
@@ -207,21 +229,21 @@ impl fmt::Display for Duration {
207229
}
208230
}
209231

210-
/// Human readable size (bytes)
211-
struct Size(usize);
232+
/// Human readable size (some units)
233+
struct Size<'a>(usize, &'a str);
212234

213-
impl fmt::Display for Size {
235+
impl<'a> fmt::Display for Size<'a> {
214236
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215-
const KIB: f64 = 1024.0;
216-
const MIB: f64 = KIB * KIB;
237+
const KI: f64 = 1024.0;
238+
const MI: f64 = KI * KI;
217239
let size = self.0 as f64;
218240

219-
if size >= MIB {
220-
write!(f, "{:5.1} MiB", size / MIB) // XYZ.P MiB
221-
} else if size >= KIB {
222-
write!(f, "{:5.1} KiB", size / KIB)
241+
if size >= MI {
242+
write!(f, "{:5.1} Mi{}", size / MI, self.1) // XYZ.P Mi
243+
} else if size >= KI {
244+
write!(f, "{:5.1} Ki{}", size / KI, self.1)
223245
} else {
224-
write!(f, "{:3.0} B", size)
246+
write!(f, "{:3.0} {}", size, self.1)
225247
}
226248
}
227249
}

src/dist/component/package.rs

+140-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::dist::component::transaction::*;
77

88
use crate::dist::temp;
99
use crate::errors::*;
10+
use crate::utils::notifications::Notification;
1011
use crate::utils::utils;
1112

1213
use std::collections::HashSet;
@@ -194,13 +195,17 @@ fn set_file_perms(_dest_path: &Path, _src_path: &Path) -> Result<()> {
194195
pub struct TarPackage<'a>(DirectoryPackage, temp::Dir<'a>);
195196

196197
impl<'a> TarPackage<'a> {
197-
pub fn new<R: Read>(stream: R, temp_cfg: &'a temp::Cfg) -> Result<Self> {
198+
pub fn new<R: Read>(
199+
stream: R,
200+
temp_cfg: &'a temp::Cfg,
201+
notify_handler: Option<&'a dyn Fn(Notification<'_>)>,
202+
) -> Result<Self> {
198203
let temp_dir = temp_cfg.new_directory()?;
199204
let mut archive = tar::Archive::new(stream);
200205
// The rust-installer packages unpack to a directory called
201206
// $pkgname-$version-$target. Skip that directory when
202207
// unpacking.
203-
unpack_without_first_dir(&mut archive, &*temp_dir)?;
208+
unpack_without_first_dir(&mut archive, &*temp_dir, notify_handler)?;
204209

205210
Ok(TarPackage(
206211
DirectoryPackage::new(temp_dir.to_owned(), false)?,
@@ -209,11 +214,122 @@ impl<'a> TarPackage<'a> {
209214
}
210215
}
211216

212-
fn unpack_without_first_dir<R: Read>(archive: &mut tar::Archive<R>, path: &Path) -> Result<()> {
217+
#[cfg(windows)]
218+
mod unpacker {
219+
use std::sync::atomic::{AtomicUsize, Ordering};
220+
use std::sync::Arc;
221+
use threadpool;
222+
223+
use crate::utils::notifications::Notification;
224+
225+
pub struct Unpacker<'a> {
226+
n_files: Arc<AtomicUsize>,
227+
pool: threadpool::ThreadPool,
228+
notify_handler: Option<&'a dyn Fn(Notification<'_>)>,
229+
}
230+
231+
impl<'a> Unpacker<'a> {
232+
pub fn new(notify_handler: Option<&'a dyn Fn(Notification<'_>)>) -> Unpacker {
233+
// Defaults to hardware thread count threads; this is suitable for
234+
// our needs as IO bound operations tend to show up as write latencies
235+
// rather than close latencies, so we don't need to look at
236+
// more threads to get more IO dispatched at this stage in the process.
237+
let pool = threadpool::Builder::new()
238+
.thread_name("CloseHandle".into())
239+
.build();
240+
Unpacker {
241+
n_files: Arc::new(AtomicUsize::new(0)),
242+
pool: pool,
243+
notify_handler: notify_handler,
244+
}
245+
}
246+
247+
pub fn handle(&mut self, unpacked: tar::Unpacked) {
248+
if let tar::Unpacked::File(f) = unpacked {
249+
self.n_files.fetch_add(1, Ordering::Relaxed);
250+
let n_files = self.n_files.clone();
251+
self.pool.execute(move || {
252+
drop(f);
253+
n_files.fetch_sub(1, Ordering::Relaxed);
254+
});
255+
}
256+
}
257+
}
258+
259+
impl<'a> Drop for Unpacker<'a> {
260+
fn drop(&mut self) {
261+
// Some explanation is in order. Even though the tar we are reading from (if
262+
// any) will have had its FileWithProgress download tracking
263+
// completed before we hit drop, that is not true if we are unwinding due to a
264+
// failure, where the logical ownership of the progress bar is
265+
// ambiguous, and as the tracker itself is abstracted out behind
266+
// notifications etc we cannot just query for that. So: we assume no
267+
// more reads of the underlying tar will take place: either the
268+
// error unwinding will stop reads, or we completed; either way, we
269+
// notify finished to the tracker to force a reset to zero; we set
270+
// the units to files, show our progress, and set our units back
271+
// afterwards. The largest archives today - rust docs - have ~20k
272+
// items, and the download tracker's progress is confounded with
273+
// actual handling of data today, we synthesis a data buffer and
274+
// pretend to have bytes to deliver.
275+
self.notify_handler
276+
.map(|handler| handler(Notification::DownloadFinished));
277+
self.notify_handler
278+
.map(|handler| handler(Notification::DownloadPushUnits("handles")));
279+
let mut prev_files = self.n_files.load(Ordering::Relaxed);
280+
self.notify_handler.map(|handler| {
281+
handler(Notification::DownloadContentLengthReceived(
282+
prev_files as u64,
283+
))
284+
});
285+
if prev_files > 50 {
286+
println!("Closing {} deferred file handles", prev_files);
287+
}
288+
let buf: Vec<u8> = vec![0; prev_files];
289+
assert!(32767 > prev_files);
290+
let mut current_files = prev_files;
291+
while current_files != 0 {
292+
use std::thread::sleep;
293+
sleep(std::time::Duration::from_millis(100));
294+
prev_files = current_files;
295+
current_files = self.n_files.load(Ordering::Relaxed);
296+
let step_count = prev_files - current_files;
297+
self.notify_handler.map(|handler| {
298+
handler(Notification::DownloadDataReceived(&buf[0..step_count]))
299+
});
300+
}
301+
self.pool.join();
302+
self.notify_handler
303+
.map(|handler| handler(Notification::DownloadFinished));
304+
self.notify_handler
305+
.map(|handler| handler(Notification::DownloadPopUnits));
306+
}
307+
}
308+
}
309+
310+
#[cfg(not(windows))]
311+
mod unpacker {
312+
use crate::utils::notifications::Notification;
313+
pub struct Unpacker {}
314+
impl Unpacker {
315+
pub fn new<'a>(_notify_handler: Option<&'a dyn Fn(Notification<'_>)>) -> Unpacker {
316+
Unpacker {}
317+
}
318+
pub fn handle(&mut self, _unpacked: tar::Unpacked) {}
319+
}
320+
}
321+
322+
fn unpack_without_first_dir<'a, R: Read>(
323+
archive: &mut tar::Archive<R>,
324+
path: &Path,
325+
notify_handler: Option<&'a dyn Fn(Notification<'_>)>,
326+
) -> Result<()> {
327+
let mut unpacker = unpacker::Unpacker::new(notify_handler);
213328
let entries = archive
214329
.entries()
215330
.chain_err(|| ErrorKind::ExtractingPackage)?;
216331
let mut checked_parents: HashSet<PathBuf> = HashSet::new();
332+
217333
for entry in entries {
218334
let mut entry = entry.chain_err(|| ErrorKind::ExtractingPackage)?;
219335
let relpath = {
@@ -249,6 +365,7 @@ fn unpack_without_first_dir<R: Read>(archive: &mut tar::Archive<R>, path: &Path)
249365
entry.set_preserve_mtime(false);
250366
entry
251367
.unpack(&full_path)
368+
.map(|unpacked| unpacker.handle(unpacked))
252369
.chain_err(|| ErrorKind::ExtractingPackage)?;
253370
}
254371

@@ -277,9 +394,17 @@ impl<'a> Package for TarPackage<'a> {
277394
pub struct TarGzPackage<'a>(TarPackage<'a>);
278395

279396
impl<'a> TarGzPackage<'a> {
280-
pub fn new<R: Read>(stream: R, temp_cfg: &'a temp::Cfg) -> Result<Self> {
397+
pub fn new<R: Read>(
398+
stream: R,
399+
temp_cfg: &'a temp::Cfg,
400+
notify_handler: Option<&'a dyn Fn(Notification<'_>)>,
401+
) -> Result<Self> {
281402
let stream = flate2::read::GzDecoder::new(stream);
282-
Ok(TarGzPackage(TarPackage::new(stream, temp_cfg)?))
403+
Ok(TarGzPackage(TarPackage::new(
404+
stream,
405+
temp_cfg,
406+
notify_handler,
407+
)?))
283408
}
284409
}
285410

@@ -305,9 +430,17 @@ impl<'a> Package for TarGzPackage<'a> {
305430
pub struct TarXzPackage<'a>(TarPackage<'a>);
306431

307432
impl<'a> TarXzPackage<'a> {
308-
pub fn new<R: Read>(stream: R, temp_cfg: &'a temp::Cfg) -> Result<Self> {
433+
pub fn new<R: Read>(
434+
stream: R,
435+
temp_cfg: &'a temp::Cfg,
436+
notify_handler: Option<&'a dyn Fn(Notification<'_>)>,
437+
) -> Result<Self> {
309438
let stream = xz2::read::XzDecoder::new(stream);
310-
Ok(TarXzPackage(TarPackage::new(stream, temp_cfg)?))
439+
Ok(TarXzPackage(TarPackage::new(
440+
stream,
441+
temp_cfg,
442+
notify_handler,
443+
)?))
311444
}
312445
}
313446

0 commit comments

Comments
 (0)