From c18bdccb7f57e921e5a0e070f02b69fca3f03126 Mon Sep 17 00:00:00 2001 From: Snezhkko Date: Thu, 6 Nov 2025 11:03:28 +0200 Subject: [PATCH 1/2] fix(prune): use saturating_sub in PruneLimiter::deleted_entries_limit_left --- crates/prune/prune/src/limiter.rs | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/prune/prune/src/limiter.rs b/crates/prune/prune/src/limiter.rs index d347ecddbd5..700ba2c7708 100644 --- a/crates/prune/prune/src/limiter.rs +++ b/crates/prune/prune/src/limiter.rs @@ -96,7 +96,7 @@ impl PruneLimiter { /// Returns the number of deleted entries left before the limit is reached. pub fn deleted_entries_limit_left(&self) -> Option { - self.deleted_entries_limit.as_ref().map(|limit| limit.limit - limit.deleted) + self.deleted_entries_limit.as_ref().map(|limit| limit.limit.saturating_sub(limit.deleted)) } /// Returns the limit on the number of deleted entries (rows in the database). @@ -411,4 +411,47 @@ mod tests { sleep(Duration::new(0, 10_000_000)); // 10 milliseconds assert!(limiter.is_limit_reached(), "Limit should be reached when time limit is reached"); } + + #[test] + fn test_deleted_entries_limit_left_saturates_when_overrun() { + let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10); + // Delete more than the limit + limiter.increment_deleted_entries_count_by(12); + // Remaining should saturate to 0 (no underflow/panic) + assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); + } + + #[test] + fn test_deleted_entries_limit_left_zero_when_equal() { + let mut limiter = PruneLimiter::default().set_deleted_entries_limit(3); + limiter.increment_deleted_entries_count_by(3); + assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); + } + + #[test] + fn test_deleted_entries_limit_left_after_lowering_limit_with_set() { + // Start with higher limit and some deletions + let mut limiter = PruneLimiter::default().set_deleted_entries_limit(20); + limiter.increment_deleted_entries_count_by(15); + assert_eq!(limiter.deleted_entries_limit_left(), Some(5)); + + // Lower the limit below the current deleted count + limiter = limiter.set_deleted_entries_limit(10); + // Remaining should saturate to 0 + assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); + } + + #[test] + fn test_deleted_entries_limit_left_after_lowering_limit_with_floor() { + // Start with limit 15 and delete 14 + let mut limiter = PruneLimiter::default().set_deleted_entries_limit(15); + limiter.increment_deleted_entries_count_by(14); + + // Floor to the largest multiple of 8 <= 15, which is 8 + let denominator = NonZeroUsize::new(8).unwrap(); + let limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator); + + // Since deleted (14) > new limit (8), remaining should be 0 (saturating) + assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); + } } From e7945056f2d29f695e59610099442d137804e844 Mon Sep 17 00:00:00 2001 From: Snezhkko Date: Thu, 6 Nov 2025 16:53:47 +0200 Subject: [PATCH 2/2] correct test --- crates/prune/prune/src/limiter.rs | 38 +++++++++++-------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/crates/prune/prune/src/limiter.rs b/crates/prune/prune/src/limiter.rs index 700ba2c7708..a32e6ab2437 100644 --- a/crates/prune/prune/src/limiter.rs +++ b/crates/prune/prune/src/limiter.rs @@ -413,45 +413,33 @@ mod tests { } #[test] - fn test_deleted_entries_limit_left_saturates_when_overrun() { + fn test_deleted_entries_limit_left_saturation_and_normal() { + // less than limit → no saturation let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10); - // Delete more than the limit - limiter.increment_deleted_entries_count_by(12); - // Remaining should saturate to 0 (no underflow/panic) - assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); - } + limiter.increment_deleted_entries_count_by(3); + assert_eq!(limiter.deleted_entries_limit_left(), Some(7)); - #[test] - fn test_deleted_entries_limit_left_zero_when_equal() { + // equal to limit → saturates to 0 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(3); limiter.increment_deleted_entries_count_by(3); assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); - } - #[test] - fn test_deleted_entries_limit_left_after_lowering_limit_with_set() { - // Start with higher limit and some deletions + // overrun past limit → saturates to 0 + let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10); + limiter.increment_deleted_entries_count_by(12); + assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); + + // lowering limit via set → saturates to 0 if below deleted let mut limiter = PruneLimiter::default().set_deleted_entries_limit(20); limiter.increment_deleted_entries_count_by(15); - assert_eq!(limiter.deleted_entries_limit_left(), Some(5)); - - // Lower the limit below the current deleted count - limiter = limiter.set_deleted_entries_limit(10); - // Remaining should saturate to 0 + let limiter = limiter.set_deleted_entries_limit(10); assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); - } - #[test] - fn test_deleted_entries_limit_left_after_lowering_limit_with_floor() { - // Start with limit 15 and delete 14 + // lowering limit via floor → saturates to 0 if below deleted let mut limiter = PruneLimiter::default().set_deleted_entries_limit(15); limiter.increment_deleted_entries_count_by(14); - - // Floor to the largest multiple of 8 <= 15, which is 8 let denominator = NonZeroUsize::new(8).unwrap(); let limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator); - - // Since deleted (14) > new limit (8), remaining should be 0 (saturating) assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); } }