From 311a96bd2837d0738aa1a9157f15f97e78087d6e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 5 Feb 2025 15:19:55 -0500 Subject: [PATCH] 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. --- crates/uv/src/commands/project/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index ce1e89830..25eec5ea3 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -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::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 { + // Lock the project environment to avoid synchronization issues. + let _lock = ProjectInterpreter::lock(workspace).await?; + match ProjectInterpreter::discover( workspace, workspace.install_path().as_ref(),