Skip to content

Commit 129e762

Browse files
committed
Auto merge of #6615 - ehuss:progress-updates, r=alexcrichton
Improve progress bar flickering. This attempts to reduce the amount of flickering primarily by reducing the number of times the progress bar is updated. The outline of changes: - Don't display the progress bar for all the initial "Fresh" messages (if -v is not given). - Don't redisplay the progress bar if it hasn't changed. - Don't clear the progress bar if it is not displayed. - Don't clear the progress bar for `Message` events that don't print anything. - Drain messages in batches to avoid frequently updating the progress bar. - Only display progress bar if the queue is blocked. This significantly improves the initial "fresh" updates, but I still see some flickering during normal updates. I wasn't able to figure out why. Logging to a file and doing screen captures I see cargo is printing the progress bar <1ms after it is cleared. I'm guessing that it's just bad timing where the terminal renders just before the progress bar is displayed, and it has to wait an entire rendering cycle until it gets displayed. I tested on a variety of different terminals and OS's, but the more testing this can get the better. This unfortunately adds some brittleness of carefully clearing the progress bar before printing new messages. I don't really see an easy way to make that better since there is such a wide variety of ways a `Message` can be printed. Fixes #6574
2 parents d75d1fb + 2932713 commit 129e762

File tree

4 files changed

+146
-86
lines changed

4 files changed

+146
-86
lines changed

src/cargo/core/compiler/job_queue.rs

Lines changed: 97 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use super::{BuildContext, BuildPlan, CompileMode, Context, Kind, Unit};
2929
/// This structure is backed by the `DependencyQueue` type and manages the
3030
/// actual compilation step of each package. Packages enqueue units of work and
3131
/// then later on the entire graph is processed and compiled.
32-
pub struct JobQueue<'a> {
32+
pub struct JobQueue<'a, 'cfg> {
3333
queue: DependencyQueue<Key<'a>, Vec<(Job, Freshness)>>,
3434
tx: Sender<Message<'a>>,
3535
rx: Receiver<Message<'a>>,
@@ -39,6 +39,7 @@ pub struct JobQueue<'a> {
3939
documented: HashSet<PackageId>,
4040
counts: HashMap<PackageId, usize>,
4141
is_release: bool,
42+
progress: Progress<'cfg>,
4243
}
4344

4445
/// A helper structure for metadata about the state of a building package.
@@ -131,9 +132,10 @@ impl<'a> JobState<'a> {
131132
}
132133
}
133134

134-
impl<'a> JobQueue<'a> {
135-
pub fn new<'cfg>(bcx: &BuildContext<'a, 'cfg>) -> JobQueue<'a> {
135+
impl<'a, 'cfg> JobQueue<'a, 'cfg> {
136+
pub fn new(bcx: &BuildContext<'a, 'cfg>) -> JobQueue<'a, 'cfg> {
136137
let (tx, rx) = channel();
138+
let progress = Progress::with_style("Building", ProgressStyle::Ratio, bcx.config);
137139
JobQueue {
138140
queue: DependencyQueue::new(),
139141
tx,
@@ -144,10 +146,11 @@ impl<'a> JobQueue<'a> {
144146
documented: HashSet::new(),
145147
counts: HashMap::new(),
146148
is_release: bcx.build_config.release,
149+
progress,
147150
}
148151
}
149152

150-
pub fn enqueue<'cfg>(
153+
pub fn enqueue(
151154
&mut self,
152155
cx: &Context<'a, 'cfg>,
153156
unit: &Unit<'a>,
@@ -231,7 +234,6 @@ impl<'a> JobQueue<'a> {
231234
// successful and otherwise wait for pending work to finish if it failed
232235
// and then immediately return.
233236
let mut error = None;
234-
let mut progress = Progress::with_style("Building", ProgressStyle::Ratio, cx.bcx.config);
235237
let total = self.queue.len();
236238
loop {
237239
// Dequeue as much work as we can, learning about everything
@@ -276,76 +278,80 @@ impl<'a> JobQueue<'a> {
276278
// to the jobserver itself.
277279
tokens.truncate(self.active.len() - 1);
278280

279-
let count = total - self.queue.len();
280-
let active_names = self
281-
.active
282-
.iter()
283-
.map(Key::name_for_progress)
284-
.collect::<Vec<_>>();
285-
drop(progress.tick_now(count, total, &format!(": {}", active_names.join(", "))));
286-
let event = self.rx.recv().unwrap();
287-
progress.clear();
288-
289-
match event {
290-
Message::Run(cmd) => {
291-
cx.bcx
292-
.config
293-
.shell()
294-
.verbose(|c| c.status("Running", &cmd))?;
295-
}
296-
Message::BuildPlanMsg(module_name, cmd, filenames) => {
297-
plan.update(&module_name, &cmd, &filenames)?;
298-
}
299-
Message::Stdout(out) => {
300-
println!("{}", out);
301-
}
302-
Message::Stderr(err) => {
303-
let mut shell = cx.bcx.config.shell();
304-
shell.print_ansi(err.as_bytes())?;
305-
shell.err().write_all(b"\n")?;
306-
}
307-
Message::FixDiagnostic(msg) => {
308-
print.print(&msg)?;
309-
}
310-
Message::Finish(key, result) => {
311-
info!("end: {:?}", key);
312-
313-
// self.active.remove_item(&key); // <- switch to this when stabilized.
314-
let pos = self
315-
.active
316-
.iter()
317-
.position(|k| *k == key)
318-
.expect("an unrecorded package has finished compiling");
319-
self.active.remove(pos);
320-
if !self.active.is_empty() {
321-
assert!(!tokens.is_empty());
322-
drop(tokens.pop());
281+
// Drain all events at once to avoid displaying the progress bar
282+
// unnecessarily.
283+
let events: Vec<_> = self.rx.try_iter().collect();
284+
let events = if events.is_empty() {
285+
self.show_progress(total);
286+
vec![self.rx.recv().unwrap()]
287+
} else {
288+
events
289+
};
290+
291+
for event in events {
292+
match event {
293+
Message::Run(cmd) => {
294+
cx.bcx
295+
.config
296+
.shell()
297+
.verbose(|c| c.status("Running", &cmd))?;
298+
}
299+
Message::BuildPlanMsg(module_name, cmd, filenames) => {
300+
plan.update(&module_name, &cmd, &filenames)?;
323301
}
324-
match result {
325-
Ok(()) => self.finish(key, cx)?,
326-
Err(e) => {
327-
let msg = "The following warnings were emitted during compilation:";
328-
self.emit_warnings(Some(msg), &key, cx)?;
329-
330-
if !self.active.is_empty() {
331-
error = Some(failure::format_err!("build failed"));
332-
handle_error(&e, &mut *cx.bcx.config.shell());
333-
cx.bcx.config.shell().warn(
334-
"build failed, waiting for other \
335-
jobs to finish...",
336-
)?;
337-
} else {
338-
error = Some(e);
302+
Message::Stdout(out) => {
303+
println!("{}", out);
304+
}
305+
Message::Stderr(err) => {
306+
let mut shell = cx.bcx.config.shell();
307+
shell.print_ansi(err.as_bytes())?;
308+
shell.err().write_all(b"\n")?;
309+
}
310+
Message::FixDiagnostic(msg) => {
311+
print.print(&msg)?;
312+
}
313+
Message::Finish(key, result) => {
314+
info!("end: {:?}", key);
315+
316+
// self.active.remove_item(&key); // <- switch to this when stabilized.
317+
let pos = self
318+
.active
319+
.iter()
320+
.position(|k| *k == key)
321+
.expect("an unrecorded package has finished compiling");
322+
self.active.remove(pos);
323+
if !self.active.is_empty() {
324+
assert!(!tokens.is_empty());
325+
drop(tokens.pop());
326+
}
327+
match result {
328+
Ok(()) => self.finish(key, cx)?,
329+
Err(e) => {
330+
let msg = "The following warnings were emitted during compilation:";
331+
self.emit_warnings(Some(msg), &key, cx)?;
332+
333+
if !self.active.is_empty() {
334+
error = Some(failure::format_err!("build failed"));
335+
handle_error(&e, &mut *cx.bcx.config.shell());
336+
cx.bcx.config.shell().warn(
337+
"build failed, waiting for other \
338+
jobs to finish...",
339+
)?;
340+
} else {
341+
error = Some(e);
342+
}
339343
}
340344
}
341345
}
342-
}
343-
Message::Token(acquired_token) => {
344-
tokens.push(acquired_token.chain_err(|| "failed to acquire jobserver token")?);
346+
Message::Token(acquired_token) => {
347+
tokens.push(
348+
acquired_token.chain_err(|| "failed to acquire jobserver token")?,
349+
);
350+
}
345351
}
346352
}
347353
}
348-
drop(progress);
354+
self.progress.clear();
349355

350356
let build_type = if self.is_release { "release" } else { "dev" };
351357
// NOTE: This may be a bit inaccurate, since this may not display the
@@ -384,6 +390,19 @@ impl<'a> JobQueue<'a> {
384390
}
385391
}
386392

393+
fn show_progress(&mut self, total: usize) {
394+
let count = total - self.queue.len();
395+
let active_names = self
396+
.active
397+
.iter()
398+
.map(Key::name_for_progress)
399+
.collect::<Vec<_>>();
400+
drop(
401+
self.progress
402+
.tick_now(count, total, &format!(": {}", active_names.join(", "))),
403+
);
404+
}
405+
387406
/// Executes a job in the `scope` given, pushing the spawned thread's
388407
/// handled onto `threads`.
389408
fn run(
@@ -422,27 +441,27 @@ impl<'a> JobQueue<'a> {
422441
}
423442

424443
fn emit_warnings(
425-
&self,
444+
&mut self,
426445
msg: Option<&str>,
427446
key: &Key<'a>,
428447
cx: &mut Context<'_, '_>,
429448
) -> CargoResult<()> {
430449
let output = cx.build_state.outputs.lock().unwrap();
431450
let bcx = &mut cx.bcx;
432451
if let Some(output) = output.get(&(key.pkg, key.kind)) {
433-
if let Some(msg) = msg {
434-
if !output.warnings.is_empty() {
452+
if !output.warnings.is_empty() {
453+
if let Some(msg) = msg {
435454
writeln!(bcx.config.shell().err(), "{}\n", msg)?;
436455
}
437-
}
438456

439-
for warning in output.warnings.iter() {
440-
bcx.config.shell().warn(warning)?;
441-
}
457+
for warning in output.warnings.iter() {
458+
bcx.config.shell().warn(warning)?;
459+
}
442460

443-
if !output.warnings.is_empty() && msg.is_some() {
444-
// Output an empty line.
445-
writeln!(bcx.config.shell().err())?;
461+
if msg.is_some() {
462+
// Output an empty line.
463+
writeln!(bcx.config.shell().err())?;
464+
}
446465
}
447466
}
448467

src/cargo/core/compiler/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl Executor for DefaultExecutor {
126126

127127
fn compile<'a, 'cfg: 'a>(
128128
cx: &mut Context<'a, 'cfg>,
129-
jobs: &mut JobQueue<'a>,
129+
jobs: &mut JobQueue<'a, 'cfg>,
130130
plan: &mut BuildPlan,
131131
unit: &Unit<'a>,
132132
exec: &Arc<dyn Executor>,

src/cargo/core/shell.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub struct Shell {
2323
err: ShellOut,
2424
/// How verbose messages should be
2525
verbosity: Verbosity,
26+
/// Flag that indicates the current line needs to be cleared before
27+
/// printing. Used when a progress bar is currently displayed.
28+
needs_clear: bool,
2629
}
2730

2831
impl fmt::Debug for Shell {
@@ -75,6 +78,7 @@ impl Shell {
7578
tty: atty::is(atty::Stream::Stderr),
7679
},
7780
verbosity: Verbosity::Verbose,
81+
needs_clear: false,
7882
}
7983
}
8084

@@ -83,6 +87,7 @@ impl Shell {
8387
Shell {
8488
err: ShellOut::Write(out),
8589
verbosity: Verbosity::Verbose,
90+
needs_clear: false,
8691
}
8792
}
8893

@@ -97,10 +102,25 @@ impl Shell {
97102
) -> CargoResult<()> {
98103
match self.verbosity {
99104
Verbosity::Quiet => Ok(()),
100-
_ => self.err.print(status, message, color, justified),
105+
_ => {
106+
if self.needs_clear {
107+
self.err_erase_line();
108+
}
109+
self.err.print(status, message, color, justified)
110+
}
101111
}
102112
}
103113

114+
/// Set whether or not the next print should clear the current line.
115+
pub fn set_needs_clear(&mut self, needs_clear: bool) {
116+
self.needs_clear = needs_clear;
117+
}
118+
119+
/// True if the `needs_clear` flag is not set.
120+
pub fn is_cleared(&self) -> bool {
121+
!self.needs_clear
122+
}
123+
104124
/// Returns the width of the terminal in spaces, if any
105125
pub fn err_width(&self) -> Option<usize> {
106126
match self.err {
@@ -119,13 +139,17 @@ impl Shell {
119139

120140
/// Get a reference to the underlying writer
121141
pub fn err(&mut self) -> &mut dyn Write {
142+
if self.needs_clear {
143+
self.err_erase_line();
144+
}
122145
self.err.as_write()
123146
}
124147

125148
/// Erase from cursor to end of line.
126149
pub fn err_erase_line(&mut self) {
127150
if let ShellOut::Stream { tty: true, .. } = self.err {
128151
imp::err_erase_line(self);
152+
self.needs_clear = false;
129153
}
130154
}
131155

@@ -251,6 +275,9 @@ impl Shell {
251275

252276
/// Prints a message and translates ANSI escape code into console colors.
253277
pub fn print_ansi(&mut self, message: &[u8]) -> CargoResult<()> {
278+
if self.needs_clear {
279+
self.err_erase_line();
280+
}
254281
#[cfg(windows)]
255282
{
256283
if let ShellOut::Stream { stream, .. } = &mut self.err {
@@ -361,7 +388,7 @@ mod imp {
361388
// This is the "EL - Erase in Line" sequence. It clears from the cursor
362389
// to the end of line.
363390
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
364-
let _ = shell.err().write_all(b"\x1B[K");
391+
let _ = shell.err.as_write().write_all(b"\x1B[K");
365392
}
366393
}
367394

@@ -434,6 +461,6 @@ mod imp {
434461
fn default_err_erase_line(shell: &mut Shell) {
435462
if let Some(max_width) = imp::stderr_width() {
436463
let blank = " ".repeat(max_width);
437-
drop(write!(shell.err(), "{}\r", blank));
464+
drop(write!(shell.err.as_write(), "{}\r", blank));
438465
}
439466
}

0 commit comments

Comments
 (0)