Avoid deadlocks when multiple uv processes lock resources (#6790)

This is achieved by updating the `LockedFile::acquire` API to be async —
as in some cases we were attempting to acquire the lock synchronously,
i.e., without yielding, which blocked the runtime.

Closes https://github.com/astral-sh/uv/issues/6691 — I tested with the
reproduction there and a local release build and no longer reproduce the
deadlock with these changes.

Some additional context in the [internal Discord
thread](1278478941)
This commit is contained in:
Zanie Blue 2024-08-29 11:16:14 -05:00 committed by GitHub
parent 4f5356ed55
commit e3d5d3d26d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 58 additions and 39 deletions

View file

@ -189,22 +189,23 @@ impl PythonEnvironment {
}
/// Grab a file lock for the environment to prevent concurrent writes across processes.
pub fn lock(&self) -> Result<LockedFile, std::io::Error> {
pub async fn lock(&self) -> Result<LockedFile, std::io::Error> {
if let Some(target) = self.0.interpreter.target() {
// If we're installing into a `--target`, use a target-specific lockfile.
LockedFile::acquire(target.root().join(".lock"), target.root().user_display())
LockedFile::acquire(target.root().join(".lock"), target.root().user_display()).await
} else if let Some(prefix) = self.0.interpreter.prefix() {
// Likewise, if we're installing into a `--prefix`, use a prefix-specific lockfile.
LockedFile::acquire(prefix.root().join(".lock"), prefix.root().user_display())
LockedFile::acquire(prefix.root().join(".lock"), prefix.root().user_display()).await
} else if self.0.interpreter.is_virtualenv() {
// If the environment a virtualenv, use a virtualenv-specific lockfile.
LockedFile::acquire(self.0.root.join(".lock"), self.0.root.user_display())
LockedFile::acquire(self.0.root.join(".lock"), self.0.root.user_display()).await
} else {
// Otherwise, use a global lockfile.
LockedFile::acquire(
env::temp_dir().join(format!("uv-{}.lock", cache_key::cache_digest(&self.0.root))),
self.0.root.user_display(),
)
.await
}
}

View file

@ -124,7 +124,7 @@ impl PythonInstallation {
let installations = ManagedPythonInstallations::from_settings()?.init()?;
let installations_dir = installations.root();
let cache_dir = installations.cache();
let _lock = installations.acquire_lock()?;
let _lock = installations.lock().await?;
let download = ManagedPythonDownload::from_request(&request)?;
let client = client_builder.build();

View file

@ -69,12 +69,10 @@ impl ManagedPythonInstallations {
Self { root: root.into() }
}
/// Lock the toolchains directory.
pub fn acquire_lock(&self) -> Result<LockedFile, Error> {
Ok(LockedFile::acquire(
self.root.join(".lock"),
self.root.user_display(),
)?)
/// Grab a file lock for the managed Python distribution directory to prevent concurrent access
/// across processes.
pub async fn lock(&self) -> Result<LockedFile, Error> {
Ok(LockedFile::acquire(self.root.join(".lock"), self.root.user_display()).await?)
}
/// Prefer, in order: