#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bulkhead {
capacity: usize,
in_flight: usize,
}
impl Bulkhead {
pub const fn new(capacity: usize) -> Self {
let capacity = if capacity == 0 { 1 } else { capacity };
Self {
capacity,
in_flight: 0,
}
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn in_flight(&self) -> usize {
self.in_flight
}
pub const fn available(&self) -> usize {
self.capacity - self.in_flight
}
pub const fn is_full(&self) -> bool {
self.in_flight >= self.capacity
}
pub const fn is_empty(&self) -> bool {
self.in_flight == 0
}
pub fn try_acquire(&mut self, permits: usize) -> bool {
if permits > self.capacity {
return false;
}
if self.available() >= permits {
self.in_flight += permits;
true
} else {
false
}
}
pub fn try_acquire_one(&mut self) -> bool {
self.try_acquire(1)
}
pub fn release(&mut self, permits: usize) {
self.in_flight = self.in_flight.saturating_sub(permits);
}
pub fn release_one(&mut self) {
self.release(1);
}
pub fn reset(&mut self) {
self.in_flight = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_starts_empty() {
let b = Bulkhead::new(3);
assert_eq!(b.capacity(), 3);
assert_eq!(b.in_flight(), 0);
assert_eq!(b.available(), 3);
assert!(b.is_empty());
assert!(!b.is_full());
}
#[test]
fn capacity_clamped_to_one() {
let mut b = Bulkhead::new(0);
assert_eq!(b.capacity(), 1);
assert!(b.try_acquire_one());
assert!(!b.try_acquire_one());
}
#[test]
fn acquire_until_full_then_reject() {
let mut b = Bulkhead::new(2);
assert!(b.try_acquire_one());
assert!(b.try_acquire_one());
assert!(b.is_full());
assert!(!b.try_acquire_one());
assert_eq!(b.in_flight(), 2);
}
#[test]
fn release_frees_room() {
let mut b = Bulkhead::new(1);
assert!(b.try_acquire_one());
assert!(!b.try_acquire_one());
b.release_one();
assert!(b.is_empty());
assert!(b.try_acquire_one());
}
#[test]
fn batch_acquire_all_or_nothing() {
let mut b = Bulkhead::new(5);
assert!(b.try_acquire(3));
assert_eq!(b.available(), 2);
assert!(!b.try_acquire(3));
assert_eq!(b.available(), 2);
assert!(b.try_acquire(2));
assert!(b.is_full());
}
#[test]
fn acquire_more_than_capacity_always_fails() {
let mut b = Bulkhead::new(4);
assert!(!b.try_acquire(5));
assert_eq!(b.in_flight(), 0);
}
#[test]
fn acquire_zero_succeeds_and_reserves_nothing() {
let mut b = Bulkhead::new(2);
assert!(b.try_acquire(0));
assert_eq!(b.in_flight(), 0);
}
#[test]
fn release_saturates_at_zero() {
let mut b = Bulkhead::new(2);
assert!(b.try_acquire_one());
b.release(100);
assert_eq!(b.in_flight(), 0);
assert!(b.is_empty());
b.release_one();
assert_eq!(b.in_flight(), 0);
}
#[test]
fn reset_clears_all_permits() {
let mut b = Bulkhead::new(4);
assert!(b.try_acquire(3));
b.reset();
assert!(b.is_empty());
assert_eq!(b.available(), 4);
}
#[test]
fn available_never_underflows_at_capacity() {
let mut b = Bulkhead::new(usize::MAX);
assert!(b.try_acquire(usize::MAX));
assert!(b.is_full());
assert_eq!(b.available(), 0);
assert!(!b.try_acquire_one());
}
}