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 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 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 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 - 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 let mut deleted_entries_limit = pruner.deleted_entries_limit.clone().unwrap();
237
238 deleted_entries_limit.deleted = 5;
240 assert!(deleted_entries_limit.is_limit_reached());
241
242 pruner = pruner.set_deleted_entries_limit(10);
244 deleted_entries_limit = pruner.deleted_entries_limit.unwrap();
245 assert_eq!(deleted_entries_limit.limit, 10);
246 assert_eq!(deleted_entries_limit.deleted, 0);
248 assert!(!deleted_entries_limit.is_limit_reached());
249 }
250
251 #[test]
252 fn test_floor_deleted_entries_limit_to_multiple_of() {
253 let limiter = PruneLimiter::default().set_deleted_entries_limit(15);
254 let denominator = NonZeroUsize::new(4).unwrap();
255
256 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
258 assert_eq!(updated_limiter.deleted_entries_limit.unwrap().limit, 12);
259
260 let limiter = PruneLimiter::default().set_deleted_entries_limit(16);
262 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
263 assert_eq!(updated_limiter.deleted_entries_limit.unwrap().limit, 16);
264
265 let limiter = PruneLimiter::default();
267 let updated_limiter = limiter.floor_deleted_entries_limit_to_multiple_of(denominator);
268 assert!(updated_limiter.deleted_entries_limit.is_none());
269 }
270
271 #[test]
272 fn test_is_deleted_entries_limit_reached() {
273 let limiter = PruneLimiter::default();
275 assert!(!limiter.is_deleted_entries_limit_reached());
276
277 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
279 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 5;
280 assert!(!limiter.is_deleted_entries_limit_reached());
282
283 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 10;
285 assert!(limiter.is_deleted_entries_limit_reached());
287
288 limiter.deleted_entries_limit.as_mut().unwrap().deleted = 12;
290 assert!(limiter.is_deleted_entries_limit_reached());
292 }
293
294 #[test]
295 fn test_increment_deleted_entries_count_by() {
296 let mut limiter = PruneLimiter::default();
298 limiter.increment_deleted_entries_count_by(5);
299 assert_eq!(limiter.deleted_entries_limit.as_ref().map(|l| l.deleted), None); let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
303 limiter.increment_deleted_entries_count_by(3);
304 assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 3); limiter.increment_deleted_entries_count_by(2);
308 assert_eq!(limiter.deleted_entries_limit.as_ref().unwrap().deleted, 5); }
310
311 #[test]
312 fn test_increment_deleted_entries_count() {
313 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(5);
314 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); }
319
320 #[test]
321 fn test_deleted_entries_limit_left() {
322 let mut limiter = PruneLimiter::default().set_deleted_entries_limit(10);
324 limiter.increment_deleted_entries_count_by(3); assert_eq!(limiter.deleted_entries_limit_left(), Some(7)); limiter = PruneLimiter::default().set_deleted_entries_limit(5);
329 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); }
339
340 #[test]
341 fn test_set_time_limit() {
342 let mut limiter = PruneLimiter::default();
344
345 limiter = limiter.set_time_limit(Duration::new(5, 0));
347
348 assert!(limiter.time_limit.is_some());
350 let time_limit = limiter.time_limit.as_ref().unwrap();
351 assert_eq!(time_limit.limit, Duration::new(5, 0));
352 assert!(time_limit.start.elapsed() < Duration::new(1, 0));
354 }
355
356 #[test]
357 fn test_is_time_limit_reached() {
358 let mut limiter = PruneLimiter::default();
360
361 assert!(!limiter.is_time_limit_reached(), "Time limit should not be reached yet");
363
364 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");
369
370 sleep(Duration::new(0, 10_000_000)); assert!(limiter.is_time_limit_reached(), "Time limit should be reached now");
373 }
374
375 #[test]
376 fn test_is_limit_reached() {
377 let mut limiter = PruneLimiter::default();
379
380 assert!(!limiter.is_limit_reached(), "Limit should not be reached with no limits set");
382
383 limiter = limiter.set_deleted_entries_limit(5);
385 assert!(
386 !limiter.is_limit_reached(),
387 "Limit should not be reached when deleted entries are less than limit"
388 );
389
390 limiter.increment_deleted_entries_count_by(5);
392 assert!(
393 limiter.is_limit_reached(),
394 "Limit should be reached when deleted entries equal the limit"
395 );
396
397 limiter = PruneLimiter::default();
399
400 limiter = limiter.set_time_limit(Duration::new(0, 10_000_000)); sleep(Duration::new(0, 5_000_000)); assert!(
406 !limiter.is_limit_reached(),
407 "Limit should not be reached when time limit not reached"
408 );
409
410 sleep(Duration::new(0, 10_000_000)); assert!(limiter.is_limit_reached(), "Limit should be reached when time limit is reached");
413 }
414}