Make project and interpreter lock acquisition non-fatal (#14404)

## Summary

If we fail to acquire a lock on an environment, uv shouldn't fail; we
should just warn. In some cases, users run uv with read-only permissions
for their projects, etc.

For now, I kept any locks acquired _in the cache_ as hard failures,
since we always need write-access to the cache.

Closes https://github.com/astral-sh/uv/issues/14411.
This commit is contained in:
Charlie Marsh 2025-07-02 14:03:43 -04:00 committed by GitHub
parent 2f53ea5c5c
commit 743260b1f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 143 additions and 21 deletions

View file

@ -25,7 +25,7 @@ use tempfile::TempDir;
use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument};
use tracing::{Instrument, debug, info_span, instrument, warn};
use uv_cache_key::cache_digest;
use uv_configuration::PreviewMode;
@ -456,8 +456,12 @@ impl SourceBuild {
"uv-setuptools-{}.lock",
cache_digest(&canonical_source_path)
));
source_tree_lock =
Some(LockedFile::acquire(lock_path, self.source_tree.to_string_lossy()).await?);
source_tree_lock = LockedFile::acquire(lock_path, self.source_tree.to_string_lossy())
.await
.inspect_err(|err| {
warn!("Failed to acquire build lock: {err}");
})
.ok();
}
Ok(source_tree_lock)
}

View file

@ -5,7 +5,7 @@ use std::path::PathBuf;
use anyhow::Context;
use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::{Level, debug, enabled};
use tracing::{Level, debug, enabled, warn};
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
@ -236,7 +236,13 @@ pub(crate) async fn pip_install(
}
}
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Determine the markers to use for the resolution.
let interpreter = environment.interpreter();

View file

@ -3,7 +3,7 @@ use std::fmt::Write;
use anyhow::{Context, Result};
use owo_colors::OwoColorize;
use tracing::debug;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
@ -211,7 +211,13 @@ pub(crate) async fn pip_sync(
}
}
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
let interpreter = environment.interpreter();

View file

@ -3,7 +3,7 @@ use std::fmt::Write;
use anyhow::Result;
use itertools::{Either, Itertools};
use owo_colors::OwoColorize;
use tracing::debug;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
@ -100,7 +100,13 @@ pub(crate) async fn pip_uninstall(
}
}
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Index the current `site-packages` directory.
let site_packages = uv_installer::SitePackages::from_environment(&environment)?;

View file

@ -10,7 +10,7 @@ use anyhow::{Context, Result, bail};
use itertools::Itertools;
use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap};
use tracing::debug;
use tracing::{debug, warn};
use url::Url;
use uv_cache::Cache;
@ -319,7 +319,13 @@ pub(crate) async fn add(
}
};
let _lock = target.acquire_lock().await?;
let _lock = target
.acquire_lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
let client_builder = BaseClientBuilder::new()
.connectivity(network_settings.connectivity)

View file

@ -1244,7 +1244,12 @@ impl ProjectEnvironment {
preview: PreviewMode,
) -> Result<Self, ProjectError> {
// Lock the project environment to avoid synchronization issues.
let _lock = ProjectInterpreter::lock(workspace).await?;
let _lock = ProjectInterpreter::lock(workspace)
.await
.inspect_err(|err| {
warn!("Failed to acquire project environment lock: {err}");
})
.ok();
let upgradeable = preview.is_enabled()
&& python
@ -1462,7 +1467,13 @@ impl ScriptEnvironment {
preview: PreviewMode,
) -> Result<Self, ProjectError> {
// Lock the script environment to avoid synchronization issues.
let _lock = ScriptInterpreter::lock(script).await?;
let _lock = ScriptInterpreter::lock(script)
.await
.inspect_err(|err| {
warn!("Failed to acquire script environment lock: {err}");
})
.ok();
let upgradeable = python_request
.as_ref()
.is_none_or(|request| !request.includes_patch());

View file

@ -5,7 +5,7 @@ use std::str::FromStr;
use anyhow::{Context, Result};
use owo_colors::OwoColorize;
use tracing::debug;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_configuration::{
@ -281,7 +281,13 @@ pub(crate) async fn remove(
}
};
let _lock = target.acquire_lock().await?;
let _lock = target
.acquire_lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Determine the lock mode.
let mode = if locked {

View file

@ -240,7 +240,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.await?
.into_environment()?;
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Determine the lock mode.
let mode = if frozen {
@ -386,7 +392,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
)
});
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
match update_environment(
environment,
@ -699,7 +711,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.map(|lock| (lock, project.workspace().install_path().to_owned()));
}
} else {
let _lock = venv.lock().await?;
let _lock = venv
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Determine the lock mode.
let mode = if frozen {

View file

@ -6,6 +6,7 @@ use std::sync::Arc;
use anyhow::{Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::warn;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
@ -169,7 +170,13 @@ pub(crate) async fn sync(
),
};
let _lock = environment.lock().await?;
let _lock = environment
.lock()
.await
.inspect_err(|err| {
warn!("Failed to acquire environment lock: {err}");
})
.ok();
// Notify the user of any environment changes.
match &environment {

View file

@ -3,13 +3,14 @@ use assert_cmd::prelude::*;
use assert_fs::{fixture::ChildPath, prelude::*};
use indoc::{formatdoc, indoc};
use insta::assert_snapshot;
use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path};
use predicates::prelude::predicate;
use tempfile::tempdir_in;
use uv_fs::Simplified;
use uv_static::EnvVars;
use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path};
#[test]
fn sync() -> Result<()> {
let context = TestContext::new("3.12");
@ -9989,3 +9990,54 @@ fn sync_url_with_query_parameters() -> Result<()> {
Ok(())
}
#[test]
#[cfg(unix)]
fn read_only() -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
"#,
)?;
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);
assert!(context.temp_dir.child("uv.lock").exists());
// Remove the flock.
fs_err::remove_file(context.venv.child(".lock"))?;
// Make the virtual environment read and execute (but not write).
fs_err::set_permissions(&context.venv, std::fs::Permissions::from_mode(0o555))?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Audited 1 package in [TIME]
");
Ok(())
}