Use a flock to avoid concurrent initialization of project environments (#11259)

## Summary

If you `uv run` from the same directory via multiple processes at the
same time, some of them will fail as they'll see an "incomplete" virtual
environment.

Closes https://github.com/astral-sh/uv/issues/11219.
This commit is contained in:
Charlie Marsh 2025-02-05 15:19:55 -05:00 committed by GitHub
parent 239f3d76b9
commit 311a96bd28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 17 additions and 1 deletions

View File

@ -8,6 +8,7 @@ use owo_colors::OwoColorize;
use tracing::debug;
use uv_cache::Cache;
use uv_cache_key::cache_digest;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DevGroupsManifest, DevGroupsSpecification, ExtrasSpecification,
@ -18,7 +19,7 @@ use uv_distribution::DistributionDatabase;
use uv_distribution_types::{
Index, Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification,
};
use uv_fs::{Simplified, CWD};
use uv_fs::{LockedFile, Simplified, CWD};
use uv_git::ResolvedRepositoryReference;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
@ -743,6 +744,18 @@ impl ProjectInterpreter {
ProjectInterpreter::Environment(venv) => venv.into_interpreter(),
}
}
/// Grab a file lock for the environment to prevent concurrent writes across processes.
pub(crate) async fn lock(workspace: &Workspace) -> Result<LockedFile, std::io::Error> {
LockedFile::acquire(
std::env::temp_dir().join(format!(
"uv-{}.lock",
cache_digest(workspace.install_path())
)),
workspace.install_path().user_display(),
)
.await
}
}
/// The source of a `Requires-Python` specifier.
@ -929,6 +942,9 @@ pub(crate) async fn get_or_init_environment(
cache: &Cache,
printer: Printer,
) -> Result<PythonEnvironment, ProjectError> {
// Lock the project environment to avoid synchronization issues.
let _lock = ProjectInterpreter::lock(workspace).await?;
match ProjectInterpreter::discover(
workspace,
workspace.install_path().as_ref(),