From d737c4a6856c01a54c3ec1eea9ee4b70d76f67ab Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 26 Jan 2025 11:30:41 -0500 Subject: [PATCH 1/3] fix!: Rename `Exponential` backoff to `Quadratic` The `gix_utils::backoff::Exponential` type actually implemented quadratic, not exponential, backoff. This renames it from `Exponential` to `Quadratic`. In exponential backoff, delays are a fixed base, often 2, raised to a power of a number that increases by one with each attempt. When the number that increases by one with each attempt is the base, raised to a fixed power, that is quadratic backoff. The intended behavior here was quadratic, as implemented. For example, in the tests, `EXPECTED_TILL_SECOND` lists the values 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, and so on, which are ascending squares. If they were an exponential sequence, then they would look like 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, and so on. Thus, it is only the named that needed to be changed: the implementation was already as intended. --- gix-utils/src/backoff.rs | 18 +++++++++--------- gix-utils/tests/backoff/mod.rs | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/gix-utils/src/backoff.rs b/gix-utils/src/backoff.rs index 66d66302440..adc8f30086c 100644 --- a/gix-utils/src/backoff.rs +++ b/gix-utils/src/backoff.rs @@ -9,17 +9,17 @@ fn randomize(backoff_ms: usize) -> usize { } } -/// A utility to calculate steps for exponential backoff similar to how it's done in `git`. -pub struct Exponential { +/// A utility to calculate steps for quadratic backoff similar to how it's done in `git`. +pub struct Quadratic { multiplier: usize, max_multiplier: usize, exponent: usize, transform: Fn, } -impl Default for Exponential usize> { +impl Default for Quadratic usize> { fn default() -> Self { - Exponential { + Quadratic { multiplier: 1, max_multiplier: 1000, exponent: 1, @@ -28,10 +28,10 @@ impl Default for Exponential usize> { } } -impl Exponential usize> { - /// Create a new exponential backoff iterator that backs off in randomized, ever increasing steps. +impl Quadratic usize> { + /// Create a new quadratic backoff iterator that backs off in randomized, ever increasing steps. pub fn default_with_random() -> Self { - Exponential { + Quadratic { multiplier: 1, max_multiplier: 1000, exponent: 1, @@ -40,7 +40,7 @@ impl Exponential usize> { } } -impl Exponential +impl Quadratic where Transform: Fn(usize) -> usize, { @@ -62,7 +62,7 @@ where } } -impl Iterator for Exponential +impl Iterator for Quadratic where Transform: Fn(usize) -> usize, { diff --git a/gix-utils/tests/backoff/mod.rs b/gix-utils/tests/backoff/mod.rs index ca9077841ca..769ed916691 100644 --- a/gix-utils/tests/backoff/mod.rs +++ b/gix-utils/tests/backoff/mod.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use gix_utils::backoff::Exponential; +use gix_utils::backoff::Quadratic; const EXPECTED_TILL_SECOND: &[usize] = &[ 1usize, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, @@ -8,9 +8,9 @@ const EXPECTED_TILL_SECOND: &[usize] = &[ ]; #[test] -fn random_exponential_produces_values_in_the_correct_range() { +fn random_quadratic_produces_values_in_the_correct_range() { let mut num_identities = 0; - for (actual, expected) in Exponential::default_with_random().zip(EXPECTED_TILL_SECOND) { + for (actual, expected) in Quadratic::default_with_random().zip(EXPECTED_TILL_SECOND) { let actual: usize = actual.as_millis().try_into().unwrap(); if actual == *expected { num_identities += 1; @@ -33,9 +33,9 @@ fn random_exponential_produces_values_in_the_correct_range() { #[test] fn how_many_iterations_for_a_second_of_waittime() { let max = Duration::from_millis(1000); - assert_eq!(Exponential::default().until_no_remaining(max).count(), 14); + assert_eq!(Quadratic::default().until_no_remaining(max).count(), 14); assert_eq!( - Exponential::default() + Quadratic::default() .until_no_remaining(max) .reduce(|acc, n| acc + n) .unwrap(), @@ -47,7 +47,7 @@ fn how_many_iterations_for_a_second_of_waittime() { #[test] fn output_with_default_settings() { assert_eq!( - Exponential::default().take(33).collect::>(), + Quadratic::default().take(33).collect::>(), EXPECTED_TILL_SECOND .iter() .map(|n| Duration::from_millis(*n as u64)) From bf4fa1b5e4a59399284dce4da51943f034e8023c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 27 Jan 2025 03:48:35 -0500 Subject: [PATCH 2/3] Adapt to changes in `gix-utils` `Exponential` was renamed to `Quadratic` in `gix_utils::backoff`. This updates the use (and comment) in `gix-lock` accordingly. --- gix-lock/src/acquire.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gix-lock/src/acquire.rs b/gix-lock/src/acquire.rs index 750e5b76af4..2c993dbc4ed 100644 --- a/gix-lock/src/acquire.rs +++ b/gix-lock/src/acquire.rs @@ -14,7 +14,7 @@ pub enum Fail { /// Fail after the first unsuccessful attempt of obtaining a lock. #[default] Immediately, - /// Retry after failure with exponentially longer sleep times to block the current thread. + /// Retry after failure with quadratically longer sleep times to block the current thread. /// Fail once the given duration is exceeded, similar to [Fail::Immediately] AfterDurationWithBackoff(Duration), } @@ -176,7 +176,7 @@ fn lock_with_mode( 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) { + for wait in backoff::Quadratic::default_with_random().until_no_remaining(time) { attempts += 1; match try_lock(&lock_path, directory, cleanup.clone()) { Ok(v) => return Ok((lock_path, v)), From 6990f76a88b0a629a1bd578979218f5e791a862e Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 26 Jan 2025 12:31:16 -0500 Subject: [PATCH 3/3] Comment forthcoming dependency changes for gix-testtools - Add a TODO comment about changing `Exponential` to `Quadratic`. This cannot be done yet, but when upgrading dependencies, it will need to be done (since the rename is a breaking change). - Replace the TODO comment about `login_shell()` with one about how to use the value of `shell()` on Windows, and modify the last component, since #1758 replaced `login_shell()` with `shell()`. `shell()` returns a path to `sh`, and gix-testtools currently needs `bash` for fixtures. But the specific approach for finding `sh.exe` relative to the Git for Windows `git-core` directory also works for `bash.exe`. --- tests/tools/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index cb91c0b87c6..82510d627de 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -219,6 +219,7 @@ pub fn spawn_git_daemon(working_dir: impl AsRef) -> std::io::Result, S: AsRef>( } fn bash_program() -> &'static Path { - // TODO: use `gix_path::env::login_shell()` when available. if cfg!(windows) { + // TODO(deps): Once `gix_path::env::shell()` is available, maybe do `shell().parent()?.join("bash.exe")` static GIT_BASH: Lazy> = Lazy::new(|| { GIT_CORE_DIR .parent()?