summaryrefslogtreecommitdiffstats
path: root/vendor/gix-lock/src/acquire.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-lock/src/acquire.rs
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-lock/src/acquire.rs')
-rw-r--r--vendor/gix-lock/src/acquire.rs168
1 files changed, 168 insertions, 0 deletions
diff --git a/vendor/gix-lock/src/acquire.rs b/vendor/gix-lock/src/acquire.rs
new file mode 100644
index 000000000..93655a7da
--- /dev/null
+++ b/vendor/gix-lock/src/acquire.rs
@@ -0,0 +1,168 @@
+use std::{
+ fmt,
+ path::{Path, PathBuf},
+ time::Duration,
+};
+
+use gix_tempfile::{AutoRemove, ContainingDirectory};
+
+use crate::{backoff, File, Marker, DOT_LOCK_SUFFIX};
+
+/// Describe what to do if a lock cannot be obtained as it's already held elsewhere.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub enum Fail {
+ /// Fail after the first unsuccessful attempt of obtaining a lock.
+ Immediately,
+ /// Retry after failure with exponentially longer sleep times to block the current thread.
+ /// Fail once the given duration is exceeded, similar to [Fail::Immediately]
+ AfterDurationWithBackoff(Duration),
+}
+
+impl Default for Fail {
+ fn default() -> Self {
+ Fail::Immediately
+ }
+}
+
+impl fmt::Display for Fail {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Fail::Immediately => f.write_str("immediately"),
+ Fail::AfterDurationWithBackoff(duration) => {
+ write!(f, "after {:.02}s", duration.as_secs_f32())
+ }
+ }
+ }
+}
+
+/// The error returned when acquiring a [`File`] or [`Marker`].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("Another IO error occurred while obtaining the lock")]
+ Io(#[from] std::io::Error),
+ #[error("The lock for resource '{resource_path}' could not be obtained {mode} after {attempts} attempt(s). The lockfile at '{resource_path}{}' might need manual deletion.", super::DOT_LOCK_SUFFIX)]
+ PermanentlyLocked {
+ resource_path: PathBuf,
+ mode: Fail,
+ attempts: usize,
+ },
+}
+
+impl File {
+ /// Create a writable lock file with failure `mode` whose content will eventually overwrite the given resource `at_path`.
+ ///
+ /// If `boundary_directory` is given, non-existing directories will be created automatically and removed in the case of
+ /// a rollback. Otherwise the containing directory is expected to exist, even though the resource doesn't have to.
+ pub fn acquire_to_update_resource(
+ at_path: impl AsRef<Path>,
+ mode: Fail,
+ boundary_directory: Option<PathBuf>,
+ ) -> Result<File, Error> {
+ let (lock_path, handle) = lock_with_mode(at_path.as_ref(), mode, boundary_directory, |p, d, c| {
+ gix_tempfile::writable_at(p, d, c)
+ })?;
+ Ok(File {
+ inner: handle,
+ lock_path,
+ })
+ }
+}
+
+impl Marker {
+ /// Like [`acquire_to_update_resource()`][File::acquire_to_update_resource()] but _without_ the possibility to make changes
+ /// and commit them.
+ ///
+ /// If `boundary_directory` is given, non-existing directories will be created automatically and removed in the case of
+ /// a rollback.
+ pub fn acquire_to_hold_resource(
+ at_path: impl AsRef<Path>,
+ mode: Fail,
+ boundary_directory: Option<PathBuf>,
+ ) -> Result<Marker, Error> {
+ let (lock_path, handle) = lock_with_mode(at_path.as_ref(), mode, boundary_directory, |p, d, c| {
+ gix_tempfile::mark_at(p, d, c)
+ })?;
+ Ok(Marker {
+ created_from_file: false,
+ inner: handle,
+ lock_path,
+ })
+ }
+}
+
+fn dir_cleanup(boundary: Option<PathBuf>) -> (ContainingDirectory, AutoRemove) {
+ match boundary {
+ None => (ContainingDirectory::Exists, AutoRemove::Tempfile),
+ Some(boundary_directory) => (
+ ContainingDirectory::CreateAllRaceProof(Default::default()),
+ AutoRemove::TempfileAndEmptyParentDirectoriesUntil { boundary_directory },
+ ),
+ }
+}
+
+fn lock_with_mode<T>(
+ resource: &Path,
+ mode: Fail,
+ boundary_directory: Option<PathBuf>,
+ try_lock: impl Fn(&Path, ContainingDirectory, AutoRemove) -> std::io::Result<T>,
+) -> Result<(PathBuf, T), Error> {
+ use std::io::ErrorKind::*;
+ let (directory, cleanup) = dir_cleanup(boundary_directory);
+ let lock_path = add_lock_suffix(resource);
+ let mut attempts = 1;
+ match mode {
+ Fail::Immediately => try_lock(&lock_path, directory, cleanup),
+ Fail::AfterDurationWithBackoff(time) => {
+ for wait in backoff::Exponential::default_with_random().until_no_remaining(time) {
+ attempts += 1;
+ match try_lock(&lock_path, directory, cleanup.clone()) {
+ Ok(v) => return Ok((lock_path, v)),
+ #[cfg(windows)]
+ Err(err) if err.kind() == AlreadyExists || err.kind() == PermissionDenied => {
+ std::thread::sleep(wait);
+ continue;
+ }
+ #[cfg(not(windows))]
+ Err(err) if err.kind() == AlreadyExists => {
+ std::thread::sleep(wait);
+ continue;
+ }
+ Err(err) => return Err(Error::from(err)),
+ }
+ }
+ try_lock(&lock_path, directory, cleanup)
+ }
+ }
+ .map(|v| (lock_path, v))
+ .map_err(|err| match err.kind() {
+ AlreadyExists => Error::PermanentlyLocked {
+ resource_path: resource.into(),
+ mode,
+ attempts,
+ },
+ _ => Error::Io(err),
+ })
+}
+
+fn add_lock_suffix(resource_path: &Path) -> PathBuf {
+ resource_path.with_extension(resource_path.extension().map_or_else(
+ || DOT_LOCK_SUFFIX.chars().skip(1).collect(),
+ |ext| format!("{}{}", ext.to_string_lossy(), DOT_LOCK_SUFFIX),
+ ))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn add_lock_suffix_to_file_with_extension() {
+ assert_eq!(add_lock_suffix(Path::new("hello.ext")), Path::new("hello.ext.lock"));
+ }
+
+ #[test]
+ fn add_lock_suffix_to_file_without_extension() {
+ assert_eq!(add_lock_suffix(Path::new("hello")), Path::new("hello.lock"));
+ }
+}