//! Descriptor-budget backpressure with no partial claim on EAGAIN. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidQueue, InvalidHead, InvalidCount, DuplicateHead, Again, UnknownHead, NotReset } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Claim { pub head: u16, pub generation: u64, pub descriptors: u16 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub used_descriptors: u16, pub live_jobs: u16, pub next_generation: u64, slots: [Option; N], } pub struct Budget { capacity: u16, used_descriptors: u16, live_jobs: u16, next_generation: u64, slots: [Option; N], } impl Budget { pub fn new(capacity: u16) -> Result { if capacity == 0 || !capacity.is_power_of_two() || capacity > 1024 || usize::from(capacity) > N { return Err(Error::InvalidQueue); } Ok(Self {capacity, used_descriptors: 0, live_jobs: 0, next_generation: 1, slots: [None; N]}) } pub fn snapshot(&self) -> Snapshot { Snapshot {used_descriptors: self.used_descriptors, live_jobs: self.live_jobs, next_generation: self.next_generation, slots: self.slots} } pub fn claim(&mut self, head: u16, descriptors: u16) -> Result { if head >= self.capacity { return Err(Error::InvalidHead); } if descriptors == 0 || descriptors > self.capacity { return Err(Error::InvalidCount); } if self.slots[usize::from(head)].is_some() { return Err(Error::DuplicateHead); } if descriptors > self.capacity - self.used_descriptors { return Err(Error::Again); } let generation = self.next_generation; self.next_generation = generation.checked_add(1).ok_or(Error::Again)?; let claim = Claim {head, generation, descriptors}; self.slots[usize::from(head)] = Some(claim); self.used_descriptors += descriptors; self.live_jobs += 1; Ok(claim) } pub fn complete(&mut self, head: u16) -> Result { if head >= self.capacity { return Err(Error::UnknownHead); } let index = usize::from(head); let claim = self.slots[index].ok_or(Error::UnknownHead)?; self.slots[index] = None; self.used_descriptors -= claim.descriptors; self.live_jobs -= 1; Ok(claim) } pub fn observe_reset(&mut self, status: u8, queue_ready: u16) -> Result<(), Error> { if status != 0 || queue_ready != 0 { return Err(Error::NotReset); } self.used_descriptors = 0; self.live_jobs = 0; self.slots = [None; N]; Ok(()) } }