1use reth_prune_types::{PruneInterruptReason, PruneProgress};
2use std::{
3 num::NonZeroUsize,
4 time::{Duration, Instant},
5};
6
7#[derive(Debug, Clone, Default)]
10pub struct PruneLimiter {
11 deleted_entries_limit: Option<PruneDeletedEntriesLimit>,
13 time_limit: Option<PruneTimeLimit>,
15}
16
17#[derive(Debug, Clone)]
18struct PruneDeletedEntriesLimit {
19 limit: usize,
21 deleted: usize,
23}
24
25impl PruneDeletedEntriesLimit {
26 const fn new(limit: usize) -> Self {
27 Self { limit, deleted: 0 }
28 }
29
30 const fn is_limit_reached(&self) -> bool {
31 self.deleted >= self.limit
32 }
33}
34
35#[derive(Debug, Clone)]
36struct PruneTimeLimit {
37 limit: Duration,
39 start: Instant,
41}
42
43impl PruneTimeLimit {
44 fn new(limit: Duration) -> Self {
45 Self { limit, start: Instant::now() }
46 }
47
48 fn is_limit_reached(&self) -> bool {
49 self.start.elapsed() > self.limit
50 }
51}
52
53impl PruneLimiter {
54 pub const fn set_deleted_entries_limit(mut self, limit: usize) -> Self {
57 if let Some(deleted_entries_limit) = self.deleted_entries_limit.as_mut() {
58 deleted_entries_limit.limit = limit;
59 } else {
60 self.deleted_entries_limit = Some(PruneDeletedEntriesLimit::new(limit));
61 }
62
63 self
64 }
65
66 pub fn floor_deleted_entries_limit_to_multiple_of(mut self, denominator: NonZeroUsize) -> Self {
71 if let Some(deleted_entries_limit) = self.deleted_entries_limit.as_mut() {
72 deleted_entries_limit.limit =
73 (deleted_entries_limit.limit / denominator) * denominator.get();
74 }
75
76 self
77 }
78
79 pub fn is_deleted_entries_limit_reached(&self) -> bool {
82 self.deleted_entries_limit.as_ref().is_some_and(|limit| limit.is_limit_reached())
83 }
84
85 pub const fn increment_deleted_entries_count_by(&mut self, entries: usize) {
87 if let Some(limit) = self.deleted_entries_limit.as_mut() {
88 limit.deleted += entries;
89 }
90 }
91
92 pub const fn increment_deleted_entries_count(&mut self) {
94 self.increment_deleted_entries_count_by(1)
95 }
96
97 pub fn deleted_entries_limit_left(&self) -> Option<usize> {
99 self.deleted_entries_limit.as_ref().map(|limit| limit.limit.saturating_sub(limit.deleted))
100 }
101
102 pub fn deleted_entries_limit(&self) -> Option<usize> {
104 self.deleted_entries_limit.as_ref().map(|limit| limit.limit)
105 }
106
107 pub fn set_time_limit(mut self, limit: Duration) -> Self {
109 self.time_limit = Some(PruneTimeLimit::new(limit));
110
111 self
112 }
113
114 pub fn is_time_limit_reached(&self) -> bool {
116 self.time_limit.as_ref().is_some_and(|limit| limit.is_limit_reached())
117 }
118
119 pub fn is_limit_reached(&self) -> bool {
121 self.is_deleted_entries_limit_reached() || self.is_time_limit_reached()
122 }
123
124 pub fn interrupt_reason(&self) -> PruneInterruptReason {
126 if self.is_time_limit_reached() {
127 PruneInterruptReason::Timeout
128 } else if self.is_deleted_entries_limit_reached() {
129 PruneInterruptReason::DeletedEntriesLimitReached
130 } else {
131 PruneInterruptReason::Unknown
132 }
133 }
134
135 pub fn progress(&self, done: bool) -> PruneProgress {
141 if done {
142 PruneProgress::Finished
143 } else {
144 PruneProgress::HasMoreData(self.interrupt_reason())
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use std::thread::sleep;
153
154 #[test]
155 fn test_prune_deleted_entries_limit_initial_state() {
156 let limit_tracker = PruneDeletedEntriesLimit::new(10);
157 assert_eq!(limit_tracker.limit, 10);
159 assert_eq!(limit_tracker.deleted, 0);
161 assert!(!limit_tracker.is_limit_reached());
162 }
163
164 #[test]
165 fn test_prune_deleted_entries_limit_is_limit_reached() {
166 let mut limit_tracker = PruneDeletedEntriesLimit::new(5);
168 limit_tracker.deleted = 3;
169 assert!(!limit_tracker.is_limit_reached());
170
171 limit_tracker.deleted = 5;
173 assert!(limit_tracker.is_limit_reached());
174
175 limit_tracker.deleted = 6;
177 assert!(limit_tracker.is_limit_reached());
178 }
179
180 #[test]
181 fn test_prune_time_limit_initial_state() {
182 let time_limit = PruneTimeLimit::new(Duration::from_secs(10));
183 assert_eq!(time_limit.limit, Duration::from_secs(10));
185 assert!(time_limit.start.elapsed() < Duration::from_secs(1));
187 assert!(!time_limit.is_limit_reached());
189 }
190
191 #[test]
192 fn test_prune_time_limit_is_limit_reached() {
193 let time_limit = PruneTimeLimit::new(Duration::from_millis(50));
194
195 std::thread::sleep(Duration::from_millis(30));
197 assert!(!time_limit.is_limit_reached());
198
199 std::thread::sleep(Duration::from_millis(30));
201 assert!(time_limit.is_limit_reached());
202 }
203
204 #[test]
205 fn test_set_deleted_entries_limit_initial_state() {
206 let pruner = PruneLimiter::default().set_deleted_entries_limit(100);
207 assert!(pruner.deleted_entries_limit.is_some());
209 let deleted_entries_limit = pruner.deleted_entries_limit.unwrap();
210 assert_eq!(deleted_entries_limit.limit, 100);
211 assert_eq!(deleted_entries_limit.deleted, 0);
213 assert!(!deleted_entries_limit.is_limit_reached());
215 }
216
217 #[test]
218 fn test_set_deleted_entries_limit_overwrite_existing() {
219 let mut pruner = PruneLimiter::default().set_deleted_entries_limit(50);
220 pruner = pruner.set_deleted_entries_limit(200);
222
223 assert!(pruner.deleted_entries_limit.is_some());
224 let deleted_entries_limit = pruner.deleted_entries_limit.unwrap();
225 assert_eq!(deleted_entries_limit.limit, 200);
227 assert_eq!(deleted_entries_limit.deleted, 0);
229 assert!(!deleted_entries_limit.is_limit_reached());
230 }
231
232 #[test]
233 fn test_set_deleted_entries_limit_when_limit_is_reached() {
234 let mut pruner = PruneLimiter::default().set_deleted_entries_limit(5);
235 assert!(pruner.deleted_entries_limit.is_some());
236
237 pruner.increment_deleted_entries_count_by(5);
239 assert!(pruner.is_deleted_entries_limit_reached());
240
241 pruner = pruner.set_deleted_entries_limit(10);
243 let deleted_entries_limit = pruner.deleted_entries_limit.unwrap();
244 assert_eq!(deleted_entries_limit.limit, 10);
245 assert_eq!(deleted_entries_limit.deleted, 5);
247 assert!(!deleted_entries_limit.is_limit_reached());
248 }
249
250 #[test]
251 fn test_floor_deleted_entries_limit_to_multiple_of() {
252 let limiter = PruneLimiter::default().set_deleted_entries_limit(15);
253 let denominator = NonZeroUsize::new(4).unwrap();
254
255 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
257 assert_eq!(updated_limiter.deleted_entries_limit.unwrap().limit, 12);
258
259 let limiter = PruneLimiter::default().set_deleted_entries_limit(16);
261 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
262 assert_eq!(updated_limiter.deleted_entries_limit.unwrap().limit, 16);
263
264 let limiter = PruneLimiter::default();
266 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
267 assert!(updated_limiter.deleted_entries_limit.is_none());
268 }
269
270 #[test]
271 fn test_is_deleted_entries_limit_reached() {
272 let limiter = PruneLimiter::default();
274 assert!(!limiter.is_deleted_entries_limit_reached());
275
276 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
278 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 5;
279 assert!(!limiter.is_deleted_entries_limit_reached());
281
282 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 10;
284 assert!(limiter.is_deleted_entries_limit_reached());
286
287 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 12;
289 assert!(limiter.is_deleted_entries_limit_reached());
291 }
292
293 #[test]
294 fn test_increment_deleted_entries_count_by() {
295 let mut limiter = PruneLimiter::default();
297 limiter.increment_deleted_entries_count_by(5);
298 assert_eq!(limiter.deleted_entries_limit.as_ref().map(|l| l.deleted), None); let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
302 limiter.increment_deleted_entries_count_by(3);
303 assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 3); limiter.increment_deleted_entries_count_by(2);
307 assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 5); }
309
310 #[test]
311 fn test_increment_deleted_entries_count() {
312 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(5);
313 assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 0); limiter.increment_deleted_entries_count(); assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 1); }
318
319 #[test]
320 fn test_deleted_entries_limit_left() {
321 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
323 limiter.increment_deleted_entries_count_by(3); assert_eq!(limiter.deleted_entries_limit_left(), Some(7)); limiter = PruneLimiter::default().set_deleted_entries_limit(5);
328 assert_eq!(limiter.deleted_entries_limit_left(), Some(5)); limiter.increment_deleted_entries_count_by(5); assert_eq!(limiter.deleted_entries_limit_left(), Some(0)); limiter = PruneLimiter::default(); assert_eq!(limiter.deleted_entries_limit_left(), None); }
338
339 #[test]
340 fn test_set_time_limit() {
341 let mut limiter = PruneLimiter::default();
343
344 limiter = limiter.set_time_limit(Duration::new(5, 0));
346
347 assert!(limiter.time_limit.is_some());
349 let time_limit = limiter.time_limit.as_ref().unwrap();
350 assert_eq!(time_limit.limit, Duration::new(5, 0));
351 assert!(time_limit.start.elapsed() < Duration::new(1, 0));
353 }
354
355 #[test]
356 fn test_is_time_limit_reached() {
357 let mut limiter = PruneLimiter::default();
359
360 assert!(!limiter.is_time_limit_reached(), "Time limit should not be reached yet");
362
363 limiter = limiter.set_time_limit(Duration::new(0, 10_000_000)); sleep(Duration::new(0, 5_000_000)); assert!(!limiter.is_time_limit_reached(), "Time limit should not be reached yet");
368
369 sleep(Duration::new(0, 10_000_000)); assert!(limiter.is_time_limit_reached(), "Time limit should be reached now");
372 }
373
374 #[test]
375 fn test_is_limit_reached() {
376 let mut limiter = PruneLimiter::default();
378
379 assert!(!limiter.is_limit_reached(), "Limit should not be reached with no limits set");
381
382 limiter = limiter.set_deleted_entries_limit(5);
384 assert!(
385 !limiter.is_limit_reached(),
386 "Limit should not be reached when deleted entries are less than limit"
387 );
388
389 limiter.increment_deleted_entries_count_by(5);
391 assert!(
392 limiter.is_limit_reached(),
393 "Limit should be reached when deleted entries equal the limit"
394 );
395
396 limiter = PruneLimiter::default();
398
399 limiter = limiter.set_time_limit(Duration::new(0, 10_000_000)); sleep(Duration::new(0, 5_000_000)); assert!(
405 !limiter.is_limit_reached(),
406 "Limit should not be reached when time limit not reached"
407 );
408
409 sleep(Duration::new(0, 10_000_000)); assert!(limiter.is_limit_reached(), "Limit should be reached when time limit is reached");
412 }
413
414 #[test]
415 fn test_deleted_entries_limit_left_saturation_and_normal() {
416 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
418 limiter.increment_deleted_entries_count_by(3);
419 assert_eq!(limiter.deleted_entries_limit_left(), Some(7));
420
421 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(3);
423 limiter.increment_deleted_entries_count_by(3);
424 assert_eq!(limiter.deleted_entries_limit_left(), Some(0));
425
426 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
428 limiter.increment_deleted_entries_count_by(12);
429 assert_eq!(limiter.deleted_entries_limit_left(), Some(0));
430
431 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(20);
433 limiter.increment_deleted_entries_count_by(15);
434 let limiter = limiter.set_deleted_entries_limit(10);
435 assert_eq!(limiter.deleted_entries_limit_left(), Some(0));
436
437 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(15);
439 limiter.increment_deleted_entries_count_by(14);
440 let denominator = NonZeroUsize::new(8).unwrap();
441 let limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
442 assert_eq!(limiter.deleted_entries_limit_left(), Some(0));
443 }
444}