1pub use state::{CapacityError, CapacityTracker, ConsumerCapacity};
2
3mod state {
4 use crate::pressure::signal::PressureSignal;
5
6 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12 pub struct ConsumerCapacity {
13 pub max_in_flight: usize,
15 pub max_buffer_depth: usize,
17 }
18
19 impl ConsumerCapacity {
20 pub const fn new(
26 max_in_flight: usize,
27 max_buffer_depth: usize,
28 ) -> Result<Self, CapacityError> {
29 if max_in_flight == 0 || max_buffer_depth == 0 {
30 Err(CapacityError::InvalidCapacity {
31 max_in_flight,
32 max_buffer_depth,
33 })
34 } else {
35 Ok(Self {
36 max_in_flight,
37 max_buffer_depth,
38 })
39 }
40 }
41
42 pub const fn validate(&self) -> Result<(), CapacityError> {
48 if self.max_in_flight == 0 || self.max_buffer_depth == 0 {
49 Err(CapacityError::InvalidCapacity {
50 max_in_flight: self.max_in_flight,
51 max_buffer_depth: self.max_buffer_depth,
52 })
53 } else {
54 Ok(())
55 }
56 }
57 }
58
59 #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
61 pub enum CapacityError {
62 #[error("consumer capacity limits must be positive")]
64 InvalidCapacity {
65 max_in_flight: usize,
67 max_buffer_depth: usize,
69 },
70 #[error("cannot decrement in-flight count below zero")]
72 InFlightUnderflow,
73 #[error("cannot decrement buffer depth below zero")]
75 BufferUnderflow,
76 }
77
78 #[derive(Clone, Debug, PartialEq, Eq)]
80 pub struct CapacityTracker {
81 capacity: ConsumerCapacity,
82 current_in_flight: usize,
83 current_buffer_depth: usize,
84 }
85
86 impl CapacityTracker {
87 #[must_use]
89 pub const fn new(capacity: ConsumerCapacity) -> Self {
90 Self {
91 capacity,
92 current_in_flight: 0,
93 current_buffer_depth: 0,
94 }
95 }
96
97 #[must_use]
116 pub const fn derived(capacity: ConsumerCapacity, queued: usize) -> Self {
117 let max_in_flight = capacity.max_in_flight;
118 let current_in_flight = if queued < max_in_flight {
119 queued
120 } else {
121 max_in_flight
122 };
123 Self {
124 capacity,
125 current_in_flight,
126 current_buffer_depth: queued.saturating_sub(max_in_flight),
127 }
128 }
129
130 #[must_use]
132 pub const fn capacity(&self) -> &ConsumerCapacity {
133 &self.capacity
134 }
135
136 #[must_use]
138 pub const fn current_in_flight(&self) -> usize {
139 self.current_in_flight
140 }
141
142 #[must_use]
144 pub const fn current_buffer_depth(&self) -> usize {
145 self.current_buffer_depth
146 }
147
148 pub const fn record_delivery(&mut self) {
150 if self.current_in_flight < usize::MAX {
151 self.current_in_flight += 1;
152 }
153 }
154
155 pub const fn record_completion(&mut self) -> Result<(), CapacityError> {
161 if self.current_in_flight == 0 {
162 Err(CapacityError::InFlightUnderflow)
163 } else {
164 self.current_in_flight -= 1;
165 Ok(())
166 }
167 }
168
169 pub const fn record_buffered(&mut self) {
171 if self.current_buffer_depth < usize::MAX {
172 self.current_buffer_depth += 1;
173 }
174 }
175
176 pub const fn record_buffer_drained(&mut self) -> Result<(), CapacityError> {
182 if self.current_buffer_depth == 0 {
183 Err(CapacityError::BufferUnderflow)
184 } else {
185 self.current_buffer_depth -= 1;
186 Ok(())
187 }
188 }
189
190 #[must_use]
192 pub const fn pressure_signal(&self) -> PressureSignal {
193 if self.current_in_flight < self.capacity.max_in_flight {
194 PressureSignal::accept(self.current_in_flight, self.capacity.max_in_flight)
195 } else if self.current_buffer_depth < self.capacity.max_buffer_depth {
196 PressureSignal::defer(
197 self.current_in_flight,
198 self.capacity.max_in_flight,
199 self.current_buffer_depth,
200 self.capacity.max_buffer_depth,
201 )
202 } else {
203 PressureSignal::reject(
204 self.current_in_flight,
205 self.capacity.max_in_flight,
206 self.current_buffer_depth,
207 self.capacity.max_buffer_depth,
208 )
209 }
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::{CapacityError, CapacityTracker, ConsumerCapacity};
217 use crate::pressure::PressureSignal;
218
219 const fn capacity(max_in_flight: usize, max_buffer_depth: usize) -> ConsumerCapacity {
220 ConsumerCapacity {
221 max_in_flight,
222 max_buffer_depth,
223 }
224 }
225
226 #[test]
237 fn derived_counters_satisfy_the_occupancy_invariant_at_every_depth() {
238 let declared = capacity(4, 8);
239 let bound = declared.max_in_flight + declared.max_buffer_depth;
240 for queued in 0..=(bound + 5) {
244 let tracker = CapacityTracker::derived(declared, queued);
245 assert_eq!(
246 tracker.current_in_flight() + tracker.current_buffer_depth(),
247 queued,
248 "occupancy invariant must hold at depth {queued}"
249 );
250 assert!(
251 tracker.current_in_flight() <= declared.max_in_flight,
252 "the in-flight band never exceeds the declared window at depth {queued}"
253 );
254 }
255 }
256
257 #[test]
260 fn derived_counters_reproduce_the_accept_defer_reject_bands() {
261 let declared = capacity(4, 8);
262 assert_eq!(
263 CapacityTracker::derived(declared, 0).pressure_signal(),
264 PressureSignal::accept(0, 4)
265 );
266 assert_eq!(
267 CapacityTracker::derived(declared, 3).pressure_signal(),
268 PressureSignal::accept(3, 4),
269 "the last slot in the in-flight window still Accepts"
270 );
271 assert_eq!(
272 CapacityTracker::derived(declared, 4).pressure_signal(),
273 PressureSignal::defer(4, 4, 0, 8),
274 "a full window with an empty buffer band Defers"
275 );
276 assert_eq!(
277 CapacityTracker::derived(declared, 11).pressure_signal(),
278 PressureSignal::defer(4, 4, 7, 8),
279 "the last slot in the buffer band still Defers"
280 );
281 assert_eq!(
282 CapacityTracker::derived(declared, 12).pressure_signal(),
283 PressureSignal::reject(4, 4, 8, 8),
284 "a full buffer band Rejects"
285 );
286 assert_eq!(
289 CapacityTracker::derived(declared, 4).pressure_signal(),
290 PressureSignal::defer(4, 4, 0, 8),
291 "a resuming consumer de-escalates through Defer with no state to reset"
292 );
293 }
294
295 #[test]
296 fn consumer_capacity_constructs_with_public_fields_and_validates_positive_limits() {
297 let declaration = ConsumerCapacity {
298 max_in_flight: 10,
299 max_buffer_depth: 50,
300 };
301
302 assert_eq!(declaration.max_in_flight, 10);
303 assert_eq!(declaration.max_buffer_depth, 50);
304 assert_eq!(declaration.validate(), Ok(()));
305 assert_eq!(ConsumerCapacity::new(10, 50), Ok(declaration));
306 assert_eq!(
307 ConsumerCapacity::new(0, 50),
308 Err(CapacityError::InvalidCapacity {
309 max_in_flight: 0,
310 max_buffer_depth: 50,
311 })
312 );
313 }
314
315 #[test]
316 fn capacity_tracker_starts_empty_and_records_counts() {
317 let mut tracker = CapacityTracker::new(capacity(10, 50));
318
319 assert_eq!(tracker.current_in_flight(), 0);
320 assert_eq!(tracker.current_buffer_depth(), 0);
321 assert_eq!(tracker.capacity(), &capacity(10, 50));
322
323 tracker.record_delivery();
324 assert_eq!(tracker.current_in_flight(), 1);
325
326 assert_eq!(tracker.record_completion(), Ok(()));
327 assert_eq!(tracker.current_in_flight(), 0);
328
329 tracker.record_buffered();
330 assert_eq!(tracker.current_buffer_depth(), 1);
331
332 assert_eq!(tracker.record_buffer_drained(), Ok(()));
333 assert_eq!(tracker.current_buffer_depth(), 0);
334 }
335
336 #[test]
337 fn capacity_tracker_reports_underflow_errors_without_negative_counts() {
338 let mut tracker = CapacityTracker::new(capacity(10, 50));
339
340 assert_eq!(
341 tracker.record_completion(),
342 Err(CapacityError::InFlightUnderflow)
343 );
344 assert_eq!(tracker.current_in_flight(), 0);
345
346 assert_eq!(
347 tracker.record_buffer_drained(),
348 Err(CapacityError::BufferUnderflow)
349 );
350 assert_eq!(tracker.current_buffer_depth(), 0);
351 }
352
353 #[test]
354 fn pressure_signal_accepts_when_in_flight_capacity_is_available() {
355 let mut tracker = CapacityTracker::new(capacity(2, 5));
356 tracker.record_delivery();
357
358 assert_eq!(tracker.pressure_signal(), PressureSignal::accept(1, 2));
359 assert_eq!(tracker.current_in_flight(), 1);
360 assert_eq!(tracker.current_buffer_depth(), 0);
361 }
362
363 #[test]
364 fn pressure_signal_defers_when_in_flight_full_and_buffer_has_capacity() {
365 let mut tracker = CapacityTracker::new(capacity(2, 5));
366 tracker.record_delivery();
367 tracker.record_delivery();
368 tracker.record_buffered();
369 tracker.record_buffered();
370 tracker.record_buffered();
371
372 assert_eq!(tracker.pressure_signal(), PressureSignal::defer(2, 2, 3, 5));
373 assert_eq!(tracker.current_in_flight(), 2);
374 assert_eq!(tracker.current_buffer_depth(), 3);
375 }
376
377 #[test]
378 fn pressure_signal_rejects_when_in_flight_and_buffer_limits_are_reached() {
379 let mut tracker = CapacityTracker::new(capacity(2, 5));
380 tracker.record_delivery();
381 tracker.record_delivery();
382 tracker.record_buffered();
383 tracker.record_buffered();
384 tracker.record_buffered();
385 tracker.record_buffered();
386 tracker.record_buffered();
387
388 assert_eq!(
389 tracker.pressure_signal(),
390 PressureSignal::reject(2, 2, 5, 5)
391 );
392 assert_eq!(tracker.current_in_flight(), 2);
393 assert_eq!(tracker.current_buffer_depth(), 5);
394 }
395
396 #[test]
397 fn pressure_signal_accepts_available_in_flight_regardless_of_buffer_state() {
398 let mut tracker = CapacityTracker::new(capacity(1, 1));
399 tracker.record_buffered();
400
401 assert_eq!(tracker.pressure_signal(), PressureSignal::accept(0, 1));
402 assert_eq!(tracker.current_in_flight(), 0);
403 assert_eq!(tracker.current_buffer_depth(), 1);
404 }
405
406 #[test]
407 fn pressure_root_re_exports_capacity_types() {
408 use crate::pressure::{
409 CapacityError as RootCapacityError, CapacityTracker as RootCapacityTracker,
410 ConsumerCapacity as RootConsumerCapacity,
411 };
412
413 let mut tracker = RootCapacityTracker::new(RootConsumerCapacity {
414 max_in_flight: 1,
415 max_buffer_depth: 1,
416 });
417
418 assert_eq!(
419 tracker.record_completion(),
420 Err(RootCapacityError::InFlightUnderflow)
421 );
422 }
423}