Skip to content

Commit ed67088

Browse files
FranciscoTGouveiarami3l
authored andcommitted
feat(updates): check for updates concurrently
This change implied the introduction of the `futures_util` crate. Feedback is greately appreciated on the above. To ensure that there are no hangs, a special "if" was needed to ensure that we never try running `.buffered(0)`. Otherwise, this would cause an hang (kudos to rami3l for helping me out with this). To report progress in a concurrent way whilst maintaining order, the `indicatf` crate was used. Note that, if we are not in a *tty* the progress wil be hidden and will instead be printed after every channels is checked for updates. NB: since we are using `indicatif` to display progress, when running in a *tty*, we can use `buffer_unordered` instead of `buffered`, as `indicatif` already handles the output in the correct order. A quick benchmark (100 runs + 10 warmpu runs in a 50Mbps connection) showed that this small change yields a **1.2x** speedup.
1 parent 02de8e8 commit ed67088

File tree

3 files changed

+92
-32
lines changed

3 files changed

+92
-32
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ enum-map = "2.5.0"
5353
env_proxy = { version = "0.4.1", optional = true }
5454
flate2 = { version = "1.1.1", default-features = false, features = ["zlib-rs"] }
5555
fs_at = "0.2.1"
56+
futures-util = "0.3.31"
5657
git-testament = "0.2"
5758
home = "0.5.4"
5859
indicatif = "0.18"

src/cli/rustup_mode.rs

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ use std::{
66
path::{Path, PathBuf},
77
process::ExitStatus,
88
str::FromStr,
9+
time::Duration,
910
};
1011

1112
use anyhow::{Context, Error, Result, anyhow};
1213
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum, builder::PossibleValue};
1314
use clap_complete::Shell;
15+
use console::style;
16+
use futures_util::stream::StreamExt;
17+
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
1418
use itertools::Itertools;
1519
use tracing::{info, trace, warn};
1620
use tracing_subscriber::{EnvFilter, Registry, reload::Handle};
@@ -34,7 +38,7 @@ use crate::{
3438
install::{InstallMethod, UpdateStatus},
3539
process::{
3640
Process,
37-
terminalsource::{self, ColorableTerminal},
41+
terminalsource::{self, ColorChoice, ColorableTerminal},
3842
},
3943
toolchain::{
4044
CustomToolchainName, DistributableToolchain, LocalToolchainName,
@@ -792,45 +796,99 @@ async fn default_(
792796
}
793797

794798
async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result<utils::ExitCode> {
799+
let t = cfg.process.stdout().terminal(cfg.process);
800+
let is_a_tty = t.is_a_tty();
801+
let use_colors = matches!(t.color_choice(), ColorChoice::Auto | ColorChoice::Always);
795802
let mut update_available = false;
796-
797-
let mut t = cfg.process.stdout().terminal(cfg.process);
798803
let channels = cfg.list_channels()?;
799-
800-
for channel in channels {
801-
let (name, distributable) = channel;
802-
let current_version = distributable.show_version()?;
803-
let dist_version = distributable.show_dist_version().await?;
804-
let _ = t.attr(terminalsource::Attr::Bold);
805-
write!(t.lock(), "{name} - ")?;
806-
match (current_version, dist_version) {
807-
(None, None) => {
808-
let _ = t.fg(terminalsource::Color::Red);
809-
writeln!(t.lock(), "Cannot identify installed or update versions")?;
810-
}
811-
(Some(cv), None) => {
812-
let _ = t.fg(terminalsource::Color::Green);
813-
write!(t.lock(), "Up to date")?;
814-
let _ = t.reset();
815-
writeln!(t.lock(), " : {cv}")?;
804+
let num_channels = channels.len();
805+
// Ensure that `.buffered()` is never called with 0 as this will cause a hang.
806+
// See: https://github.com/rust-lang/futures-rs/pull/1194#discussion_r209501774
807+
if num_channels > 0 {
808+
let multi_progress_bars = if is_a_tty {
809+
MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(t)))
810+
} else {
811+
MultiProgress::with_draw_target(ProgressDrawTarget::hidden())
812+
};
813+
let channels = tokio_stream::iter(channels.into_iter()).map(|(name, distributable)| {
814+
let pb = multi_progress_bars.add(ProgressBar::new(1));
815+
pb.set_style(
816+
ProgressStyle::with_template("{msg:.bold} - Checking... {spinner:.green}")
817+
.unwrap()
818+
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
819+
);
820+
pb.set_message(format!("{name}"));
821+
pb.enable_steady_tick(Duration::from_millis(100));
822+
async move {
823+
let current_version = distributable.show_version()?;
824+
let dist_version = distributable.show_dist_version().await?;
825+
let mut update_a = false;
826+
827+
let mut styled_name = style(format!("{name} - "));
828+
if use_colors {
829+
styled_name = styled_name.bold();
830+
}
831+
let message = match (current_version, dist_version) {
832+
(None, None) => {
833+
let mut m = style("Cannot identify installed or update versions");
834+
if use_colors {
835+
m = m.red().bold();
836+
}
837+
format!("{styled_name}{m}")
838+
}
839+
(Some(cv), None) => {
840+
let mut m = style("Up to date");
841+
if use_colors {
842+
m = m.green().bold();
843+
}
844+
format!("{styled_name}{m} : {cv}")
845+
}
846+
(Some(cv), Some(dv)) => {
847+
let mut m = style("Update available");
848+
if use_colors {
849+
m = m.yellow().bold();
850+
}
851+
update_a = true;
852+
format!("{styled_name}{m} : {cv} -> {dv}")
853+
}
854+
(None, Some(dv)) => {
855+
let mut m = style("Update available");
856+
if use_colors {
857+
m = m.yellow().bold();
858+
}
859+
update_a = true;
860+
format!("{styled_name}{m} : (Unknown version) -> {dv}")
861+
}
862+
};
863+
pb.set_style(ProgressStyle::with_template(message.as_str()).unwrap());
864+
pb.finish();
865+
Ok::<(bool, String), Error>((update_a, message))
816866
}
817-
(Some(cv), Some(dv)) => {
867+
});
868+
869+
// If we are running in a TTY, we can use `buffer_unordered` since
870+
// displaying the output in the correct order is already handled by
871+
// `indicatif`.
872+
let channels = if is_a_tty {
873+
channels
874+
.buffer_unordered(num_channels)
875+
.collect::<Vec<_>>()
876+
.await
877+
} else {
878+
channels.buffered(num_channels).collect::<Vec<_>>().await
879+
};
880+
881+
let t = cfg.process.stdout().terminal(cfg.process);
882+
for result in channels {
883+
let (update_a, message) = result?;
884+
if update_a {
818885
update_available = true;
819-
let _ = t.fg(terminalsource::Color::Yellow);
820-
write!(t.lock(), "Update available")?;
821-
let _ = t.reset();
822-
writeln!(t.lock(), " : {cv} -> {dv}")?;
823886
}
824-
(None, Some(dv)) => {
825-
update_available = true;
826-
let _ = t.fg(terminalsource::Color::Yellow);
827-
write!(t.lock(), "Update available")?;
828-
let _ = t.reset();
829-
writeln!(t.lock(), " : (Unknown version) -> {dv}")?;
887+
if !is_a_tty {
888+
writeln!(t.lock(), "{message}")?;
830889
}
831890
}
832891
}
833-
834892
let self_update_mode = cfg.get_self_update_mode()?;
835893
// Priority: no-self-update feature > self_update_mode > no-self-update args.
836894
// Check for update only if rustup does **not** have the no-self-update feature,

0 commit comments

Comments
 (0)