mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
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:
parent
4f5356ed55
commit
e3d5d3d26d
19 changed files with 58 additions and 39 deletions
|
@ -24,6 +24,7 @@ fs-err = { workspace = true }
|
|||
fs2 = { workspace = true }
|
||||
path-slash = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, optional = true}
|
||||
tempfile = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
|
@ -33,4 +34,4 @@ junction = { workspace = true }
|
|||
|
||||
[features]
|
||||
default = []
|
||||
tokio = ["fs-err/tokio", "backoff/tokio"]
|
||||
tokio = ["dep:tokio", "fs-err/tokio", "backoff/tokio"]
|
||||
|
|
|
@ -314,8 +314,8 @@ pub fn is_temporary(path: impl AsRef<Path>) -> bool {
|
|||
pub struct LockedFile(fs_err::File);
|
||||
|
||||
impl LockedFile {
|
||||
pub fn acquire(path: impl AsRef<Path>, resource: impl Display) -> Result<Self, std::io::Error> {
|
||||
let file = fs_err::File::create(path.as_ref())?;
|
||||
/// Inner implementation for [`LockedFile::acquire_blocking`] and [`LockedFile::acquire`].
|
||||
fn lock_file_blocking(file: fs_err::File, resource: &str) -> Result<Self, std::io::Error> {
|
||||
trace!("Checking lock for `{resource}`");
|
||||
match file.file().try_lock_exclusive() {
|
||||
Ok(()) => {
|
||||
|
@ -328,19 +328,42 @@ impl LockedFile {
|
|||
warn_user!(
|
||||
"Waiting to acquire lock for {} (lockfile: {})",
|
||||
resource,
|
||||
path.user_display(),
|
||||
file.path().user_display(),
|
||||
);
|
||||
file.file().lock_exclusive().map_err(|err| {
|
||||
// Not an fs_err method, we need to build our own path context
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Could not lock {}: {}", path.as_ref().user_display(), err),
|
||||
format!("Could not lock {}: {}", file.path().user_display(), err),
|
||||
)
|
||||
})?;
|
||||
Ok(Self(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as [`LockedFile::acquire`], but for synchronous contexts. Do not use from an async
|
||||
/// context, as this can block the runtime while waiting for another process to release the
|
||||
/// lock.
|
||||
pub fn acquire_blocking(
|
||||
path: impl AsRef<Path>,
|
||||
resource: impl Display,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
let file = fs_err::File::create(path.as_ref())?;
|
||||
let resource = resource.to_string();
|
||||
Self::lock_file_blocking(file, &resource)
|
||||
}
|
||||
|
||||
/// Acquire a cross-process lock for a resource using a file at the provided path.
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn acquire(
|
||||
path: impl AsRef<Path>,
|
||||
resource: impl Display,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
let file = fs_err::File::create(path.as_ref())?;
|
||||
let resource = resource.to_string();
|
||||
tokio::task::spawn_blocking(move || Self::lock_file_blocking(file, &resource)).await?
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LockedFile {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue