From d1f4f8a358016a60d730ba1ca234ccfdf194ee8e Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 18 Jul 2025 14:47:56 +0200 Subject: [PATCH] More resilient registry removal (#14717) With the previous order of operations, there could be warnings from race conditions between two process A and B removing and installing Python versions. * A removes the files for CPython3.9.18 * B sees the key CPython3.9.18 * B sees that CPython3.9.18 has no files * A removes the key for CPython3.9.18 * B try to removes the key for CPython3.9.18, gets and error that it's already gone, issues a warning We make the more resilient in two ways: * We remove the registry key first, avoiding dangling registry keys in the removal process * We ignore not found errors in registry removal operations: If we try to remove something that's already gone, that's fine. Fixes #14714 (hopefully) --- crates/uv-python/src/windows_registry.rs | 6 ++++++ crates/uv/src/commands/python/uninstall.rs | 24 ++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index 7c6f6f307..f722db60c 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -268,6 +268,9 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation // Separate assignment since `keys()` creates a borrow. let subkeys = match key.keys() { Ok(subkeys) => subkeys, + Err(err) if err.code() == ERROR_NOT_FOUND => { + return; + } Err(err) => { // TODO(konsti): We don't have an installation key here. warn_user_once!("Failed to list subkeys of HKCU:\\{astral_key}: {err}"); @@ -281,6 +284,9 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation let python_entry = format!("{astral_key}\\{subkey}"); debug!("Removing orphan registry key HKCU:\\{}", python_entry); if let Err(err) = CURRENT_USER.remove_tree(&python_entry) { + if err.code() == ERROR_NOT_FOUND { + continue; + } // TODO(konsti): We don't have an installation key here. warn_user_once!("Failed to remove orphan registry key HKCU:\\{python_entry}: {err}"); } diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index dd306fc4d..c2e2e6877 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -142,6 +142,19 @@ async fn do_uninstall( return Ok(ExitStatus::Failure); } + // Remove registry entries first, so we don't have dangling entries between the file removal + // and the registry removal. + let mut errors = vec![]; + #[cfg(windows)] + { + uv_python::windows_registry::remove_registry_entry( + &matching_installations, + all, + &mut errors, + ); + uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations); + } + // Find and remove all relevant Python executables let mut uninstalled_executables: FxHashMap> = FxHashMap::default(); @@ -201,7 +214,6 @@ async fn do_uninstall( } let mut uninstalled = IndexSet::::default(); - let mut errors = vec![]; while let Some((key, result)) = tasks.next().await { if let Err(err) = result { errors.push((key.clone(), anyhow::Error::new(err))); @@ -210,16 +222,6 @@ async fn do_uninstall( } } - #[cfg(windows)] - { - uv_python::windows_registry::remove_registry_entry( - &matching_installations, - all, - &mut errors, - ); - uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations); - } - // Read all existing managed installations and find the highest installed patch // for each installed minor version. Ensure the minor version link directory // is still valid.