foyer_storage/
filter.rs

1// Copyright 2026 foyer Project Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{fmt::Debug, sync::Arc, time::Duration};
16
17use crate::io::device::statistics::Statistics;
18
19/// Filter result for admission pickers and reinsertion pickers.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum StorageFilterResult {
22    /// Admittion.
23    Admit,
24    /// Rejection.
25    Reject,
26    /// This result indicates that the disk cache is throttled caused by the current io throttle.
27    /// The minimal duration to retry this submission is returned for the caller to decide whether to retry it later.
28    Throttled(Duration),
29}
30
31impl StorageFilterResult {
32    /// Convert the filter result to a boolean value.
33    pub fn is_admitted(&self) -> bool {
34        matches!(self, StorageFilterResult::Admit)
35    }
36
37    /// Convert the filter result to a boolean value.
38    pub fn is_rejected(&self) -> bool {
39        matches!(self, StorageFilterResult::Reject)
40    }
41}
42
43/// Condition for [`StorageFilter`].
44pub trait StorageFilterCondition: Send + Sync + Debug + 'static {
45    /// Decide whether to pick an entry by hash.
46    fn filter(&self, stats: &Arc<Statistics>, hash: u64, estimated_size: usize) -> StorageFilterResult;
47}
48
49/// [`StorageFilter`] filters entries based on multiple conditions for admission and reinsertion.
50///
51/// [`StorageFilter`] admits all entries if no conditions are set.
52#[derive(Debug, Default)]
53pub struct StorageFilter {
54    conditions: Vec<Box<dyn StorageFilterCondition>>,
55}
56
57impl StorageFilter {
58    /// Create a new empty filter.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Push a new condition to the filter.
64    pub fn with_condition<C: StorageFilterCondition>(mut self, condition: C) -> Self {
65        self.conditions.push(Box::new(condition));
66        self
67    }
68
69    /// Check if the entry can be admitted by the filter conditions.
70    pub fn filter(&self, stats: &Arc<Statistics>, hash: u64, estimated_size: usize) -> StorageFilterResult {
71        let mut duration = Duration::ZERO;
72        for condition in &self.conditions {
73            match condition.filter(stats, hash, estimated_size) {
74                StorageFilterResult::Admit => {}
75                StorageFilterResult::Reject => return StorageFilterResult::Reject,
76                StorageFilterResult::Throttled(dur) => duration += dur,
77            }
78        }
79        if duration.is_zero() {
80            StorageFilterResult::Admit
81        } else {
82            StorageFilterResult::Throttled(duration)
83        }
84    }
85}
86
87pub mod conditions {
88
89    use std::ops::{Bound, Range, RangeBounds};
90
91    pub use super::*;
92
93    /// Admit all entries.
94    #[derive(Debug, Default)]
95    pub struct AdmitAll;
96
97    impl StorageFilterCondition for AdmitAll {
98        fn filter(&self, _: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
99            StorageFilterResult::Admit
100        }
101    }
102
103    /// Reject all entries.
104    #[derive(Debug, Default)]
105    pub struct RejectAll;
106
107    impl StorageFilterCondition for RejectAll {
108        fn filter(&self, _: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
109            StorageFilterResult::Reject
110        }
111    }
112
113    #[derive(Debug, Default)]
114    pub struct IoThrottle;
115
116    impl StorageFilterCondition for IoThrottle {
117        fn filter(&self, stats: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
118            let duration = stats.write_throttle();
119            if duration.is_zero() {
120                StorageFilterResult::Admit
121            } else {
122                StorageFilterResult::Throttled(duration)
123            }
124        }
125    }
126
127    /// A condition that checks if the estimated size is within a specified range.
128    #[derive(Debug)]
129    pub struct EstimatedSize {
130        range: Range<usize>,
131    }
132
133    impl EstimatedSize {
134        /// Create a new `EstimatedSize` condition with a specified range.
135        pub fn new<R: RangeBounds<usize>>(range: R) -> Self {
136            let start = match range.start_bound() {
137                Bound::Included(v) => *v,
138                Bound::Excluded(v) => *v + 1,
139                Bound::Unbounded => 0,
140            };
141            let end = match range.end_bound() {
142                Bound::Included(v) => *v + 1,
143                Bound::Excluded(v) => *v,
144                Bound::Unbounded => usize::MAX,
145            };
146            Self { range: start..end }
147        }
148    }
149
150    impl StorageFilterCondition for EstimatedSize {
151        fn filter(&self, _: &Arc<Statistics>, _: u64, estimated_size: usize) -> StorageFilterResult {
152            if self.range.contains(&estimated_size) {
153                StorageFilterResult::Admit
154            } else {
155                StorageFilterResult::Reject
156            }
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::conditions::*;
164    use crate::Throttle;
165
166    #[test]
167    fn test_estimated_size_condition() {
168        let condition = EstimatedSize::new(10..20);
169        let statistics = Arc::new(Statistics::new(Throttle::default()));
170        assert_eq!(condition.filter(&statistics, 0, 15), StorageFilterResult::Admit);
171        assert_eq!(condition.filter(&statistics, 0, 5), StorageFilterResult::Reject);
172        assert_eq!(condition.filter(&statistics, 0, 20), StorageFilterResult::Reject);
173    }
174}