Skip to main content

rocketmq_runtime/resource_budget/
limit.rs

1// Copyright 2023 The RocketMQ Rust 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::time::Duration;
16
17use crate::RuntimeContractViolation;
18
19/// Identifies the full policy state.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum FullPolicy {
22    /// Represents the reject case.
23    Reject,
24    /// Waits for capacity until the caller-provided absolute deadline.
25    WaitUntilDeadline,
26    /// Represents the coalesce latest case.
27    CoalesceLatest,
28    /// Represents the drop stale case.
29    DropStale,
30    /// Represents the close slow consumer case.
31    CloseSlowConsumer,
32}
33
34/// Identifies the budget class state.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum BudgetClass {
37    /// Represents the data case.
38    Data,
39    /// Represents the control case.
40    Control,
41}
42
43/// Identifies the budget dimension state.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum BudgetDimension {
46    /// Represents the count case.
47    Count,
48    /// Represents the bytes case.
49    Bytes,
50    /// Represents the rate case.
51    Rate,
52}
53
54/// Represents rate limit.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct RateLimit {
57    /// The permits per second value.
58    pub permits_per_second: u64,
59    /// The burst value.
60    pub burst: u64,
61}
62
63impl RateLimit {
64    #[must_use]
65    /// Creates a new `RateLimit`.
66    pub const fn new(permits_per_second: u64, burst: u64) -> Self {
67        Self {
68            permits_per_second,
69            burst,
70        }
71    }
72}
73
74/// Represents budget capacity.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct BudgetCapacity {
77    /// The count value.
78    pub count: usize,
79    /// The bytes value.
80    pub bytes: usize,
81    /// The rate value.
82    pub rate: Option<RateLimit>,
83}
84
85impl BudgetCapacity {
86    #[must_use]
87    /// Creates a new `BudgetCapacity`.
88    pub const fn new(count: usize, bytes: usize) -> Self {
89        Self {
90            count,
91            bytes,
92            rate: None,
93        }
94    }
95
96    #[must_use]
97    /// Sets rate and returns the updated value.
98    pub const fn with_rate(mut self, rate: RateLimit) -> Self {
99        self.rate = Some(rate);
100        self
101    }
102}
103
104impl Default for BudgetCapacity {
105    fn default() -> Self {
106        Self::new(0, 0)
107    }
108}
109
110/// Represents budget limit.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct BudgetLimit {
113    /// The capacity value.
114    pub capacity: BudgetCapacity,
115    /// The control reserve value.
116    pub control_reserve: BudgetCapacity,
117    /// The max age value.
118    pub max_age: Option<Duration>,
119    /// The full policy value.
120    pub full_policy: FullPolicy,
121}
122
123impl BudgetLimit {
124    #[must_use]
125    /// Creates a new `BudgetLimit`.
126    pub const fn new(count: usize, bytes: usize, full_policy: FullPolicy) -> Self {
127        Self {
128            capacity: BudgetCapacity::new(count, bytes),
129            control_reserve: BudgetCapacity::new(0, 0),
130            max_age: None,
131            full_policy,
132        }
133    }
134
135    #[must_use]
136    /// Sets rate and returns the updated value.
137    pub const fn with_rate(mut self, rate: RateLimit) -> Self {
138        self.capacity.rate = Some(rate);
139        self
140    }
141
142    #[must_use]
143    /// Sets control reserve and returns the updated value.
144    pub const fn with_control_reserve(mut self, reserve: BudgetCapacity) -> Self {
145        self.control_reserve = reserve;
146        self
147    }
148
149    #[must_use]
150    /// Sets max age and returns the updated value.
151    pub const fn with_max_age(mut self, max_age: Duration) -> Self {
152        self.max_age = Some(max_age);
153        self
154    }
155
156    pub(crate) fn validate(self, path: &str) -> Result<(), RuntimeContractViolation> {
157        if self.capacity.count == 0 {
158            return Err(RuntimeContractViolation::ZeroBudgetCapacity {
159                dimension: BudgetDimension::Count,
160            });
161        }
162        if self.capacity.bytes == 0 {
163            return Err(RuntimeContractViolation::ZeroBudgetCapacity {
164                dimension: BudgetDimension::Bytes,
165            });
166        }
167        validate_rate(path, self.capacity.rate)?;
168        validate_rate(path, self.control_reserve.rate)?;
169        if self.control_reserve.count > self.capacity.count {
170            return Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
171                dimension: BudgetDimension::Count,
172            });
173        }
174        if self.control_reserve.bytes > self.capacity.bytes {
175            return Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
176                dimension: BudgetDimension::Bytes,
177            });
178        }
179        match (self.capacity.rate, self.control_reserve.rate) {
180            (Some(capacity), Some(reserve))
181                if reserve.permits_per_second > capacity.permits_per_second || reserve.burst > capacity.burst =>
182            {
183                Err(RuntimeContractViolation::ReserveExceedsBudgetCapacity {
184                    dimension: BudgetDimension::Rate,
185                })
186            }
187            (None, Some(_)) => Err(RuntimeContractViolation::ReserveWithoutBudgetCapacity {
188                dimension: BudgetDimension::Rate,
189            }),
190            _ if self.max_age == Some(Duration::ZERO) => Err(RuntimeContractViolation::ZeroBudgetMaxAge),
191            _ => Ok(()),
192        }
193    }
194
195    pub(crate) fn validate_child(self, parent: Self, path: &str) -> Result<(), RuntimeContractViolation> {
196        self.validate(path)?;
197        if self.capacity.count > parent.capacity.count {
198            return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
199                dimension: BudgetDimension::Count,
200            });
201        }
202        if self.capacity.bytes > parent.capacity.bytes {
203            return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
204                dimension: BudgetDimension::Bytes,
205            });
206        }
207        match (self.capacity.rate, parent.capacity.rate) {
208            (Some(child), Some(parent))
209                if child.permits_per_second > parent.permits_per_second || child.burst > parent.burst =>
210            {
211                return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
212                    dimension: BudgetDimension::Rate,
213                });
214            }
215            (None, Some(_)) => {
216                return Err(RuntimeContractViolation::ChildBudgetExceedsParent {
217                    dimension: BudgetDimension::Rate,
218                });
219            }
220            _ => {}
221        }
222        match (self.max_age, parent.max_age) {
223            (Some(child), Some(parent)) if child > parent => {
224                Err(RuntimeContractViolation::ChildBudgetMaxAgeExceedsParent)
225            }
226            (None, Some(_)) => Err(RuntimeContractViolation::ChildBudgetMaxAgeExceedsParent),
227            _ => Ok(()),
228        }
229    }
230}
231
232fn validate_rate(_path: &str, rate: Option<RateLimit>) -> Result<(), RuntimeContractViolation> {
233    if let Some(rate) = rate {
234        if rate.permits_per_second == 0 || rate.burst == 0 {
235            return Err(RuntimeContractViolation::ZeroBudgetRate);
236        }
237    }
238    Ok(())
239}