memory_accounting/
verifier.rs

1use bytesize::ByteSize;
2use snafu::Snafu;
3
4use crate::{ComponentBounds, MemoryGrant};
5
6/// A verification error.
7#[derive(Debug, Eq, PartialEq, Snafu)]
8pub enum VerifierError {
9    /// Component bounds were invalid.
10    #[snafu(display("invalid component bounds for {}: {}", component_name, reason))]
11    InvalidComponentBounds {
12        /// Name of the component.
13        component_name: String,
14
15        /// Reason that the bounds were invalid.
16        reason: String,
17    },
18
19    /// Insufficient memory available to meet the minimum required memory for all components.
20    #[snafu(display(
21        "minimum require memory ({}) exceeds available memory ({})",
22        ByteSize::b(*minimum_required_bytes as u64).display().si(),
23        ByteSize::b(*available_bytes as u64).display().si(),
24    ))]
25    InsufficientMinimumMemory {
26        /// Total number of bytes available.
27        available_bytes: usize,
28
29        /// Total number of minimum required bytes.
30        minimum_required_bytes: usize,
31    },
32
33    /// Insufficient memory available to meet the firm limit for all components.
34    #[snafu(display(
35        "firm limit ({}) exceeds available memory ({})",
36        ByteSize::b(*firm_limit_bytes as u64).display().si(),
37        ByteSize::b(*available_bytes as u64).display().si(),
38    ))]
39    FirmLimitExceedsAvailable {
40        /// Total number of bytes available.
41        available_bytes: usize,
42
43        /// Total number of firm limit bytes.
44        firm_limit_bytes: usize,
45    },
46}
47
48/// Verified bounds.
49///
50/// This structure contains the original set of parameters -- the grant, verify mode, and verified components -- used
51/// when verifying bounds in `BoundsVerifier::verify`. It can then be used to feed into additional components, such as
52/// `MemoryPartitioner`, to ensure that the same parameters are used, avoiding any potential misconfiguration.
53pub struct VerifiedBounds {
54    grant: MemoryGrant,
55    component_bounds: ComponentBounds,
56}
57
58impl VerifiedBounds {
59    /// Total number of bytes available for allocation.
60    pub fn total_available_bytes(&self) -> usize {
61        self.grant.effective_limit_bytes()
62    }
63
64    /// Returns the total number of minimum required bytes for all components that were verified.
65    pub fn total_minimum_required_bytes(&self) -> usize {
66        self.component_bounds.total_minimum_required_bytes()
67    }
68
69    /// Returns the total firm limit, in bytes, for all components that were verified.
70    pub fn total_firm_limit_bytes(&self) -> usize {
71        self.component_bounds.total_firm_limit_bytes()
72    }
73
74    /// Gets a reference to the original component bounds that were verified.
75    pub fn bounds(&self) -> &ComponentBounds {
76        &self.component_bounds
77    }
78}
79
80/// Memory bounds verifier.
81pub struct BoundsVerifier {
82    grant: MemoryGrant,
83    component_bounds: ComponentBounds,
84}
85
86impl BoundsVerifier {
87    /// Creates a new memory bounds verifier with the given memory grant and components bounds.
88    pub fn new(grant: MemoryGrant, component_bounds: ComponentBounds) -> Self {
89        Self {
90            grant,
91            component_bounds,
92        }
93    }
94
95    /// Validates that all components are able to respect the calculated effective limit.
96    ///
97    /// If validation succeeds, a `MemoryGrant` is returned which provides information about the effective limit that
98    /// can be used for allocating memory.
99    ///
100    /// ## Errors
101    ///
102    /// A number of invalid conditions are checked and will cause an error to be returned:
103    ///
104    /// - when a component has invalid bounds (e.g. minimum required bytes higher than firm limit)
105    /// - when the combined total of the firm limit for all components exceeds the effective limit
106    pub fn verify(self) -> Result<VerifiedBounds, VerifierError> {
107        // Evaluate the total minimum required and firm limit bytes to make sure our memory grant is sufficient.
108        let available_bytes = self.grant.effective_limit_bytes();
109        let total_minimum_required_bytes = self.component_bounds.total_minimum_required_bytes();
110        let total_firm_limit_bytes = self.component_bounds.total_firm_limit_bytes();
111
112        if available_bytes < total_minimum_required_bytes {
113            return Err(VerifierError::InsufficientMinimumMemory {
114                available_bytes,
115                minimum_required_bytes: total_minimum_required_bytes,
116            });
117        }
118
119        if available_bytes < total_firm_limit_bytes {
120            return Err(VerifierError::FirmLimitExceedsAvailable {
121                available_bytes,
122                firm_limit_bytes: total_firm_limit_bytes,
123            });
124        }
125
126        Ok(VerifiedBounds {
127            grant: self.grant,
128            component_bounds: self.component_bounds,
129        })
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::{BoundsVerifier, VerifiedBounds, VerifierError};
136    use crate::{
137        test_util::{get_component_bounds, BoundedComponent},
138        MemoryGrant,
139    };
140
141    fn get_grant(initial_limit_bytes: usize) -> MemoryGrant {
142        const SLOP_FACTOR: f64 = 0.25;
143
144        MemoryGrant::with_slop_factor(initial_limit_bytes, SLOP_FACTOR).expect("should never be invalid")
145    }
146
147    fn verify_component(
148        initial_limit_bytes: usize, component: &BoundedComponent,
149    ) -> (MemoryGrant, Result<VerifiedBounds, VerifierError>) {
150        let initial_grant = get_grant(initial_limit_bytes);
151        let bounds = get_component_bounds(component);
152
153        let verifier = BoundsVerifier::new(initial_grant, bounds);
154        (initial_grant, verifier.verify())
155    }
156
157    #[test]
158    fn verify() {
159        let minimum_required_bytes = 10;
160        let firm_limit_bytes = 20;
161
162        // This component will have a total firm limit of 30 bytes, by adding the 20 bytes above to the 10 bytes minimum.
163        let bounded = BoundedComponent::new(Some(minimum_required_bytes), firm_limit_bytes);
164
165        // First two verifications don't have enough capacity to meet the minimum requirements, based on the slop
166        // factor: we need at least 13 bytes to meet the minimum requirements. (14 bytes * (1 - 0.25 slop factor) -> 10 bytes)
167        let (grant, result) = verify_component(1, &bounded);
168        assert_eq!(
169            result.err(),
170            Some(VerifierError::InsufficientMinimumMemory {
171                available_bytes: grant.effective_limit_bytes(),
172                minimum_required_bytes,
173            })
174        );
175
176        let (grant, result) = verify_component(10, &bounded);
177        assert_eq!(
178            result.err(),
179            Some(VerifierError::InsufficientMinimumMemory {
180                available_bytes: grant.effective_limit_bytes(),
181                minimum_required_bytes,
182            })
183        );
184
185        // Now we have enough capacity for the minimum requirements, but the firm limit exceeds that. We need at least
186        // 40 bytes to meet the firm limit requirements: (40 bytes * (1 - 0.25 slop factor) -> 30 bytes)
187        let (grant, result) = verify_component(14, &bounded);
188        assert_eq!(
189            result.err(),
190            Some(VerifierError::FirmLimitExceedsAvailable {
191                available_bytes: grant.effective_limit_bytes(),
192                firm_limit_bytes: minimum_required_bytes + firm_limit_bytes,
193            })
194        );
195
196        let (grant, result) = verify_component(30, &bounded);
197        assert_eq!(
198            result.err(),
199            Some(VerifierError::FirmLimitExceedsAvailable {
200                available_bytes: grant.effective_limit_bytes(),
201                firm_limit_bytes: minimum_required_bytes + firm_limit_bytes,
202            })
203        );
204
205        // We've finally provided enough capacity to meet the firm limit, so this should pass.
206        let (_, result) = verify_component(40, &bounded);
207        assert!(result.is_ok());
208    }
209}