From 2c5cc62106d88d8a0a94f6f91e89887433ba0da2 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 27 Aug 2024 22:25:05 +0200 Subject: [PATCH] ci: Make Windows tests ~27% faster by putting temp folder in dev drive (#6680) ## Summary This PR makes `cargo test | windows` faster in CI. ### Before ![Windows tests take 5m44s](https://github.com/user-attachments/assets/8dd9c619-9b7b-4ebd-a027-56e7967b6d34) ### After ![Windows tests take 5m12s](https://github.com/user-attachments/assets/7702fdba-3034-4db8-b211-85207a1feffa) ## Also This PR disables the `brotli` feature of `async-compression` since it's not strictly needed, but this has little to do with the improvements (it's still less code to build). This PR introduces additional code in uv tool uninstall to ignore errors (that only seem to happen on ReFS, ie. on Dev Drives) akin to "the thing we're trying to delete cannot be deleted because it's already being deleted". If `raw_os_error` was stable we could do u32 matching instead of that `.to_string().contains()` abomination. --- .github/workflows/ci.yml | 1 + Cargo.lock | 37 ------------------- Cargo.toml | 2 +- crates/uv-cache/src/lib.rs | 9 ++++- crates/uv/src/commands/tool/uninstall.rs | 46 ++++++++++++++++++++++-- 5 files changed, 54 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e68cef5..c12ea0123 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -285,6 +285,7 @@ jobs: env: CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup + UV_INTERNAL__TEST_DIR: ${{ env.DEV_DRIVE }}/tmp-uv run: | cargo nextest run --no-default-features --features python,pypi --workspace --status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow diff --git a/Cargo.lock b/Cargo.lock index 12b4b0f77..b462f2c8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,21 +43,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -211,7 +196,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ - "brotli", "bzip2", "flate2", "futures-core", @@ -461,27 +445,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510a90332002c1af3317ef6b712f0dab697f30bbe809b86965eac2923c0bca8e" -[[package]] -name = "brotli" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bstr" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index bb9090b50..8baa67eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,7 @@ quote = { version = "1.0.36" } rayon = { version = "1.8.0" } reflink-copy = { version = "0.1.15" } regex = { version = "1.10.2" } -reqwest = { version = "0.12.3", default-features = false, features = ["json", "gzip", "brotli", "stream", "rustls-tls", "rustls-tls-native-roots"] } +reqwest = { version = "0.12.3", default-features = false, features = ["json", "gzip", "stream", "rustls-tls", "rustls-tls-native-roots"] } reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } rkyv = { version = "0.7.43", features = ["strict", "validation"] } diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index 5d76ad738..0c48d0e42 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -135,7 +135,14 @@ impl Cache { /// Create a temporary cache directory. pub fn temp() -> Result { - let temp_dir = tempfile::tempdir()?; + let temp_dir = + if let Ok(test_dir) = std::env::var("UV_INTERNAL__TEST_DIR").map(PathBuf::from) { + let uv_cache_dir = test_dir.join("uv-cache"); + let _ = fs_err::create_dir_all(&uv_cache_dir); + tempfile::tempdir_in(uv_cache_dir)? + } else { + tempfile::tempdir()? + }; Ok(Self { root: temp_dir.path().to_path_buf(), refresh: Refresh::None(Timestamp::now()), diff --git a/crates/uv/src/commands/tool/uninstall.rs b/crates/uv/src/commands/tool/uninstall.rs index b43a9e606..a171743a1 100644 --- a/crates/uv/src/commands/tool/uninstall.rs +++ b/crates/uv/src/commands/tool/uninstall.rs @@ -32,10 +32,14 @@ pub(crate) async fn uninstall(name: Option, printer: Printer) -> Re // Clean up any empty directories. if uv_fs::directories(installed_tools.root()).all(|path| uv_fs::is_temporary(&path)) { - fs_err::tokio::remove_dir_all(&installed_tools.root()).await?; + fs_err::tokio::remove_dir_all(&installed_tools.root()) + .await + .ignore_currently_being_deleted()?; if let Some(top_level) = installed_tools.root().parent() { if uv_fs::directories(top_level).all(|path| uv_fs::is_temporary(&path)) { - fs_err::tokio::remove_dir_all(top_level).await?; + fs_err::tokio::remove_dir_all(top_level) + .await + .ignore_currently_being_deleted()?; } } } @@ -43,6 +47,44 @@ pub(crate) async fn uninstall(name: Option, printer: Printer) -> Re Ok(ExitStatus::Success) } +trait IoErrorExt: std::error::Error + 'static { + #[inline] + fn is_in_process_of_being_deleted(&self) -> bool { + if cfg!(target_os = "windows") { + use std::error::Error; + let mut e: &dyn Error = &self; + loop { + if e.to_string().contains("The file cannot be opened because it is in the process of being deleted. (os error 303)") { + return true; + } + e = match e.source() { + Some(e) => e, + None => break, + } + } + } + + false + } +} + +impl IoErrorExt for std::io::Error {} + +/// An extension trait to suppress "cannot open file because it's currently being deleted" +trait IgnoreCurrentlyBeingDeleted { + fn ignore_currently_being_deleted(self) -> Self; +} + +impl IgnoreCurrentlyBeingDeleted for Result<(), std::io::Error> { + fn ignore_currently_being_deleted(self) -> Self { + match self { + Ok(()) => Ok(()), + Err(err) if err.is_in_process_of_being_deleted() => Ok(()), + Err(err) => Err(err), + } + } +} + /// Perform the uninstallation. async fn do_uninstall( installed_tools: &InstalledTools,