Perform startup/init code eagerly in entry::install_crates (#880)

The startup/init code in `entry::install_crates` performs a lot of blocking operations, from testing if dir exists to reading from files and there is no `.await` in there until after the startup.

There are also a few cases where `block_in_place` should be called (e.g. loading manifests, loading TLS certificates) but is missing.

Most of the `Args` passed to `entry::install_crates` are actually consumed before the first `.await` point, so performing startup/init code eagerly would make the generated future much smaller, reduce codegen and also makes it easier to optimize.

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-03-11 15:06:46 +11:00 committed by GitHub
parent 18bc81f46f
commit c5d0b84aa6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 90 deletions

View file

@ -35,29 +35,33 @@ impl Termination for MainExit {
} }
impl MainExit { impl MainExit {
pub fn new(result: Result<Result<()>, BinstallError>, done: Duration) -> Self { pub fn new(res: Result<()>, done: Duration) -> Self {
result.map_or_else(MainExit::Error, |res| { res.map(|()| MainExit::Success(Some(done)))
res.map(|()| MainExit::Success(Some(done))) .unwrap_or_else(|err| {
.unwrap_or_else(|err| { err.downcast::<BinstallError>()
err.downcast::<BinstallError>() .map(MainExit::Error)
.map(MainExit::Error) .unwrap_or_else(MainExit::Report)
.unwrap_or_else(MainExit::Report) })
})
})
} }
} }
/// This function would start a tokio multithreading runtime, /// This function would start a tokio multithreading runtime,
/// spawn a new task on it that runs `f`, then `block_on` it. /// spawn a new task on it that runs `f()`, then `block_on` it.
/// ///
/// It will cancel the future if user requested cancellation /// It will cancel the future if user requested cancellation
/// via signal. /// via signal.
pub fn run_tokio_main<F, T>(f: F) -> Result<T, BinstallError> pub fn run_tokio_main<Func, Fut>(f: Func) -> Result<()>
where where
F: Future<Output = T> + Send + 'static, Func: FnOnce() -> Result<Option<Fut>>,
T: Send + 'static, Fut: Future<Output = Result<()>> + Send + 'static,
{ {
let rt = Runtime::new()?; let rt = Runtime::new().map_err(BinstallError::from)?;
let handle = AutoAbortJoinHandle::new(rt.spawn(f)); let _guard = rt.enter();
rt.block_on(cancel_on_user_sig_term(handle))
if let Some(fut) = f()? {
let handle = AutoAbortJoinHandle::new(rt.spawn(fut));
rt.block_on(cancel_on_user_sig_term(handle))?
} else {
Ok(())
}
} }

View file

@ -1,5 +1,6 @@
use std::{ use std::{
env, fs, env, fs,
future::Future,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
time::Duration, time::Duration,
@ -35,7 +36,10 @@ use crate::{
ui::confirm, ui::confirm,
}; };
pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -> Result<()> { pub fn install_crates(
args: Args,
jobserver_client: LazyJobserverClient,
) -> Result<Option<impl Future<Output = Result<()>>>> {
// Compute Resolvers // Compute Resolvers
let mut cargo_install_fallback = false; let mut cargo_install_fallback = false;
@ -63,7 +67,7 @@ pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -
if crate_names.peek().is_none() { if crate_names.peek().is_none() {
debug!("Nothing to do"); debug!("Nothing to do");
return Ok(()); return Ok(None);
} }
// Launch target detection // Launch target detection
@ -131,53 +135,55 @@ pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -
}) })
.collect(); .collect();
// Collect results Ok(Some(async move {
let mut resolution_fetchs = Vec::new(); // Collect results
let mut resolution_sources = Vec::new(); let mut resolution_fetchs = Vec::new();
let mut resolution_sources = Vec::new();
for task in tasks { for task in tasks {
match task.await?? { match task.await?? {
Resolution::AlreadyUpToDate => {} Resolution::AlreadyUpToDate => {}
Resolution::Fetch(fetch) => { Resolution::Fetch(fetch) => {
fetch.print(&binstall_opts); fetch.print(&binstall_opts);
resolution_fetchs.push(fetch) resolution_fetchs.push(fetch)
} }
Resolution::InstallFromSource(source) => { Resolution::InstallFromSource(source) => {
source.print(); source.print();
resolution_sources.push(source) resolution_sources.push(source)
}
} }
} }
}
if resolution_fetchs.is_empty() && resolution_sources.is_empty() { if resolution_fetchs.is_empty() && resolution_sources.is_empty() {
debug!("Nothing to do"); debug!("Nothing to do");
return Ok(()); return Ok(());
} }
// Confirm // Confirm
if !dry_run && !no_confirm { if !dry_run && !no_confirm {
confirm().await?; confirm().await?;
} }
do_install_fetches( do_install_fetches(
resolution_fetchs, resolution_fetchs,
manifests, manifests,
&binstall_opts, &binstall_opts,
dry_run, dry_run,
temp_dir, temp_dir,
no_cleanup, no_cleanup,
)?; )?;
let tasks: Vec<_> = resolution_sources let tasks: Vec<_> = resolution_sources
.into_iter() .into_iter()
.map(|source| AutoAbortJoinHandle::spawn(source.install(binstall_opts.clone()))) .map(|source| AutoAbortJoinHandle::spawn(source.install(binstall_opts.clone())))
.collect(); .collect();
for task in tasks { for task in tasks {
task.await??; task.await??;
} }
Ok(()) Ok(())
}))
} }
fn do_read_root_cert(path: &Path) -> Result<Option<Certificate>, BinstallError> { fn do_read_root_cert(path: &Path) -> Result<Option<Certificate>, BinstallError> {
@ -228,43 +234,41 @@ fn compute_paths_and_load_manifests(
roots: Option<PathBuf>, roots: Option<PathBuf>,
install_path: Option<PathBuf>, install_path: Option<PathBuf>,
) -> Result<(PathBuf, Option<Manifests>, tempfile::TempDir)> { ) -> Result<(PathBuf, Option<Manifests>, tempfile::TempDir)> {
block_in_place(|| { // Compute cargo_roots
// Compute cargo_roots let cargo_roots = install_path::get_cargo_roots_path(roots).ok_or_else(|| {
let cargo_roots = install_path::get_cargo_roots_path(roots).ok_or_else(|| { error!("No viable cargo roots path found of specified, try `--roots`");
error!("No viable cargo roots path found of specified, try `--roots`"); miette!("No cargo roots path found or specified")
miette!("No cargo roots path found or specified") })?;
})?;
// Compute install directory // Compute install directory
let (install_path, custom_install_path) = let (install_path, custom_install_path) =
install_path::get_install_path(install_path, Some(&cargo_roots)); install_path::get_install_path(install_path, Some(&cargo_roots));
let install_path = install_path.ok_or_else(|| { let install_path = install_path.ok_or_else(|| {
error!("No viable install path found of specified, try `--install-path`"); error!("No viable install path found of specified, try `--install-path`");
miette!("No install path found or specified") miette!("No install path found or specified")
})?; })?;
fs::create_dir_all(&install_path).map_err(BinstallError::Io)?; fs::create_dir_all(&install_path).map_err(BinstallError::Io)?;
debug!("Using install path: {}", install_path.display()); debug!("Using install path: {}", install_path.display());
// Load manifests // Load manifests
let manifests = if !custom_install_path { let manifests = if !custom_install_path {
Some(Manifests::open_exclusive(&cargo_roots)?) Some(Manifests::open_exclusive(&cargo_roots)?)
} else { } else {
None None
}; };
// Create a temporary directory for downloads etc. // Create a temporary directory for downloads etc.
// //
// Put all binaries to a temporary directory under `dst` first, catching // Put all binaries to a temporary directory under `dst` first, catching
// some failure modes (e.g., out of space) before touching the existing // some failure modes (e.g., out of space) before touching the existing
// binaries. This directory will get cleaned up via RAII. // binaries. This directory will get cleaned up via RAII.
let temp_dir = tempfile::Builder::new() let temp_dir = tempfile::Builder::new()
.prefix("cargo-binstall") .prefix("cargo-binstall")
.tempdir_in(&install_path) .tempdir_in(&install_path)
.map_err(BinstallError::from) .map_err(BinstallError::from)
.wrap_err("Creating a temporary directory failed.")?; .wrap_err("Creating a temporary directory failed.")?;
Ok((install_path, manifests, temp_dir)) Ok((install_path, manifests, temp_dir))
})
} }
/// Return vec of (crate_name, current_version) /// Return vec of (crate_name, current_version)

View file

@ -32,7 +32,7 @@ fn main() -> MainExit {
let start = Instant::now(); let start = Instant::now();
let result = run_tokio_main(entry::install_crates(args, jobserver_client)); let result = run_tokio_main(|| entry::install_crates(args, jobserver_client));
let done = start.elapsed(); let done = start.elapsed();
debug!("run time: {done:?}"); debug!("run time: {done:?}");