From 86c1bd1595bc5ed6b27e80f63bbfa739a804e394 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 19 Jul 2026 21:35:11 -0700 Subject: [PATCH] lock rustup across concurrent toolchain installs Fixes #629 --- crates/cargo-gpu-install/src/install.rs | 6 ++++- .../src/install_toolchain.rs | 24 +++++++++++++++++++ crates/cargo-gpu-install/src/lib.rs | 3 ++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/cargo-gpu-install/src/install.rs b/crates/cargo-gpu-install/src/install.rs index 82a13593911..bb72e088a4c 100644 --- a/crates/cargo-gpu-install/src/install.rs +++ b/crates/cargo-gpu-install/src/install.rs @@ -385,11 +385,15 @@ pub struct FileLock(std::fs::File); impl FileLock { pub fn lock(path: &Path) -> std::io::Result { + Self::lock_with_message(path, "codegen backend build") + } + + pub fn lock_with_message(path: &Path, what: &str) -> std::io::Result { let file = std::fs::File::create(path)?; match file.try_lock() { Ok(_) => (), Err(std::fs::TryLockError::WouldBlock) => { - user_output!("Waiting for file lock on codegen backend build...\n"); + user_output!("Waiting for file lock on {what}...\n"); file.lock()?; } Err(std::fs::TryLockError::Error(e)) => return Err(e), diff --git a/crates/cargo-gpu-install/src/install_toolchain.rs b/crates/cargo-gpu-install/src/install_toolchain.rs index ad06aa32b37..67e06df4d39 100644 --- a/crates/cargo-gpu-install/src/install_toolchain.rs +++ b/crates/cargo-gpu-install/src/install_toolchain.rs @@ -40,6 +40,17 @@ pub fn ensure_toolchain_and_components_exist( let host_tuple = host_tuple.trim_ascii(); let toolchain = format!("{channel}-{host_tuple}"); + // Two concurrent `cargo-gpu` processes (e.g. build scripts of multiple shader crates running + // in parallel) can otherwise observe a toolchain that another process is still in the middle + // of installing: `rustup toolchain list` already lists it, but its manifest isn't written yet, + // and the `component list` below fails with `error: Missing manifest in toolchain '...'`. + // + // So hold a lock across the entire check-and-install. It is taken unconditionally, even when + // the codegen backend is already built and the `install_dir` lock in `install.rs` is never + // taken at all, since we always query `rustup` here. + let _rustup_lock = crate::install::FileLock::lock_with_message(&rustup_lock_path()?, "rustup") + .context("acquiring rustup file lock")?; + if !is_toolchain_installed(&toolchain, host_tuple)? { let message = format!( "toolchain {channel} with components {}", @@ -72,6 +83,19 @@ pub fn ensure_toolchain_and_components_exist( Ok(()) } +/// Path of the file lock guarding our `rustup` invocations. +fn rustup_lock_path() -> anyhow::Result { + let (home, _) = run_cmd(Command::new("rustup").args(["show", "home"]))?; + // FNV-1a, needs to be stable across Rust versions + let hash = home + .trim_ascii() + .bytes() + .fold(0xcbf2_9ce4_8422_2325_u64, |hash, byte| { + (hash ^ u64::from(byte)).wrapping_mul(0x0000_0100_0000_01b3) + }); + Ok(std::env::temp_dir().join(format!("rust-gpu-rustup-{hash:016x}.lock"))) +} + /// Returns true if the toolchain and required components are installed. fn is_toolchain_installed(toolchain: &str, host_tuple: &str) -> anyhow::Result { // check if toolchain is installed diff --git a/crates/cargo-gpu-install/src/lib.rs b/crates/cargo-gpu-install/src/lib.rs index 19cc48bcf8f..fc72b69aa0e 100644 --- a/crates/cargo-gpu-install/src/lib.rs +++ b/crates/cargo-gpu-install/src/lib.rs @@ -38,7 +38,8 @@ macro_rules! user_output { #[macro_export] #[cfg(not(feature = "tty"))] macro_rules! user_output { - ($($args: tt)*) => {{}}; + // reference the args, so they don't count as unused when output is compiled out + ($($args: tt)*) => {{ let _ = format_args!($($args)*); }}; } /// The central cache directory of cargo gpu