budget_context/
reservation.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{Budget, BudgetError, Resource};
4
5#[derive(Debug)]
7#[must_use = "dropping an active reservation releases its capacity"]
8pub struct Reservation {
9 budget: Budget,
10 resource: Resource,
11 amount: u64,
12 active: bool,
13}
14
15impl Reservation {
16 pub(crate) fn new(budget: Budget, resource: Resource, amount: u64) -> Self {
17 Self {
18 budget,
19 resource,
20 amount,
21 active: amount != 0,
22 }
23 }
24
25 #[must_use]
27 pub fn resource(&self) -> &Resource {
28 &self.resource
29 }
30
31 #[must_use]
33 pub fn amount(&self) -> u64 {
34 self.amount
35 }
36
37 pub fn commit(mut self, actual: u64) -> Result<(), BudgetError> {
49 if self.amount == 0 {
50 if actual == 0 {
51 return Ok(());
52 }
53 return Err(BudgetError::ReservationExceeded {
54 resource: self.resource.clone(),
55 reserved: 0,
56 actual,
57 unaccounted: actual,
58 });
59 }
60 let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
61 let accounted = BTreeMap::from([(self.resource.clone(), actual.min(self.amount))]);
62 self.budget.commit_reserved(&reserved, &accounted);
63 self.active = false;
64 self.budget
65 .trace_event("commit", &self.resource, actual.min(self.amount));
66 if actual > self.amount {
67 return Err(BudgetError::ReservationExceeded {
68 resource: self.resource.clone(),
69 reserved: self.amount,
70 actual,
71 unaccounted: actual - self.amount,
72 });
73 }
74 Ok(())
75 }
76
77 pub fn release(mut self) {
79 if self.active {
80 let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
81 self.budget.release_reserved(&reserved);
82 self.active = false;
83 self.budget
84 .trace_event("release", &self.resource, self.amount);
85 }
86 }
87}
88
89impl Drop for Reservation {
90 fn drop(&mut self) {
91 if self.active {
92 let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
93 self.budget.release_reserved(&reserved);
94 self.active = false;
95 self.budget
96 .trace_event("release", &self.resource, self.amount);
97 }
98 }
99}
100
101#[derive(Debug)]
103#[must_use = "dropping an active reservation set releases its capacity"]
104pub struct ReservationSet {
105 budget: Budget,
106 amounts: BTreeMap<Resource, u64>,
107 active: bool,
108}
109
110impl ReservationSet {
111 pub(crate) fn new(budget: Budget, amounts: BTreeMap<Resource, u64>) -> Self {
112 let active = !amounts.is_empty();
113 Self {
114 budget,
115 amounts,
116 active,
117 }
118 }
119
120 #[must_use]
122 pub fn amounts(&self) -> &BTreeMap<Resource, u64> {
123 &self.amounts
124 }
125
126 pub fn commit<'a>(
139 mut self,
140 actual: impl IntoIterator<Item = (&'a Resource, u64)>,
141 ) -> Result<(), BudgetError> {
142 let mut actuals = BTreeMap::<Resource, u64>::new();
143 let mut overflows = BTreeSet::new();
144 for (resource, amount) in actual {
145 let current = actuals.entry(resource.clone()).or_default();
146 if let Some(sum) = current.checked_add(amount) {
147 *current = sum;
148 } else {
149 overflows.insert(resource.clone());
150 }
151 }
152 if let Some(resource) = actuals
153 .keys()
154 .find(|resource| !self.amounts.contains_key(*resource))
155 .cloned()
156 {
157 self.commit_all_inner();
158 return Err(BudgetError::UnknownReservationResource { resource });
159 }
160 if let Some(resource) = overflows.into_iter().next() {
161 self.commit_all_inner();
162 return Err(BudgetError::Overflow { resource });
163 }
164
165 let accounted = self
166 .amounts
167 .iter()
168 .map(|(resource, reserved)| {
169 let actual = actuals.get(resource).copied().unwrap_or(0);
170 (resource.clone(), actual.min(*reserved))
171 })
172 .collect();
173 self.budget.commit_reserved(&self.amounts, &accounted);
174 self.active = false;
175 for (resource, amount) in &accounted {
176 self.budget.trace_event("commit", resource, *amount);
177 }
178
179 for (resource, reserved) in &self.amounts {
180 let actual = actuals.get(resource).copied().unwrap_or(0);
181 if actual > *reserved {
182 return Err(BudgetError::ReservationExceeded {
183 resource: resource.clone(),
184 reserved: *reserved,
185 actual,
186 unaccounted: actual - reserved,
187 });
188 }
189 }
190 Ok(())
191 }
192
193 pub fn commit_all(mut self) {
195 self.commit_all_inner();
196 }
197
198 pub fn release(mut self) {
200 if self.active {
201 self.budget.release_reserved(&self.amounts);
202 self.active = false;
203 for (resource, amount) in &self.amounts {
204 self.budget.trace_event("release", resource, *amount);
205 }
206 }
207 }
208
209 fn commit_all_inner(&mut self) {
210 if self.active {
211 self.budget.commit_reserved(&self.amounts, &self.amounts);
212 self.active = false;
213 for (resource, amount) in &self.amounts {
214 self.budget.trace_event("commit", resource, *amount);
215 }
216 }
217 }
218}
219
220impl Drop for ReservationSet {
221 fn drop(&mut self) {
222 if self.active {
223 self.budget.release_reserved(&self.amounts);
224 self.active = false;
225 for (resource, amount) in &self.amounts {
226 self.budget.trace_event("release", resource, *amount);
227 }
228 }
229 }
230}