rocketmq_runtime/resource_budget/
limit.rs1use std::time::Duration;
16
17use crate::RuntimeContractViolation;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum FullPolicy {
22 Reject,
24 WaitUntilDeadline,
26 CoalesceLatest,
28 DropStale,
30 CloseSlowConsumer,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum BudgetClass {
37 Data,
39 Control,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum BudgetDimension {
46 Count,
48 Bytes,
50 Rate,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct RateLimit {
57 pub permits_per_second: u64,
59 pub burst: u64,
61}
62
63impl RateLimit {
64 #[must_use]
65 pub const fn new(permits_per_second: u64, burst: u64) -> Self {
67 Self {
68 permits_per_second,
69 burst,
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct BudgetCapacity {
77 pub count: usize,
79 pub bytes: usize,
81 pub rate: Option<RateLimit>,
83}
84
85impl BudgetCapacity {
86 #[must_use]
87 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct BudgetLimit {
113 pub capacity: BudgetCapacity,
115 pub control_reserve: BudgetCapacity,
117 pub max_age: Option<Duration>,
119 pub full_policy: FullPolicy,
121}
122
123impl BudgetLimit {
124 #[must_use]
125 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 pub const fn with_rate(mut self, rate: RateLimit) -> Self {
138 self.capacity.rate = Some(rate);
139 self
140 }
141
142 #[must_use]
143 pub const fn with_control_reserve(mut self, reserve: BudgetCapacity) -> Self {
145 self.control_reserve = reserve;
146 self
147 }
148
149 #[must_use]
150 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}