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](https://discord.com/channels/1039017663004942429/1278430431204741270/1278478941262188595)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user