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)
This commit is contained in:
konsti 2025-07-18 14:47:56 +02:00 committed by GitHub
parent 8f2f43c561
commit d1f4f8a358
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 19 additions and 11 deletions

View file

@ -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}");
}

View file

@ -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<PythonInstallationKey, FxHashSet<PathBuf>> =
FxHashMap::default();
@ -201,7 +214,6 @@ async fn do_uninstall(
}
let mut uninstalled = IndexSet::<PythonInstallationKey>::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.