use std::collections::{BTreeMap, VecDeque};
use super::super::flow_control::{ByteBudget, CreditClass, SlotCreditAccount, try_reserve_pair};
use super::super::protocol::{CloseReason, RecordType, SlotId};
use crate::streaming::sender::{cached_dropped, cached_heartbeat, is_terminal_sentinel};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Applied {
Delivered,
Held,
Duplicate,
ReaderStall,
Fault(CloseReason),
}
pub(super) struct IngressSlot {
pub(super) id: SlotId,
frame_tx: flume::Sender<Vec<u8>>,
account: SlotCreditAccount,
sizes: VecDeque<u32>,
buffered_bytes: u64,
byte_watermark: u64,
next_seq: u32,
hold: BTreeMap<u32, Vec<u8>>,
hold_bytes: ByteBudget,
pending_close: Option<(u32, CloseReason)>,
}
enum DeliverFault {
Overspend,
ReaderStall,
ConsumerGone,
}
impl IngressSlot {
pub(super) fn new(
id: SlotId,
frame_tx: flume::Sender<Vec<u8>>,
initial_credit: u32,
slot_byte_budget: u32,
first_seq: u32,
) -> Self {
Self {
id,
frame_tx,
account: SlotCreditAccount::new(initial_credit),
sizes: VecDeque::new(),
buffered_bytes: 0,
byte_watermark: u64::from(slot_byte_budget),
next_seq: first_seq,
hold: BTreeMap::new(),
hold_bytes: ByteBudget::new(u64::from(slot_byte_budget)),
pending_close: None,
}
}
pub(super) fn held(&self) -> usize {
self.hold.len()
}
pub(super) fn apply_data(
&mut self,
frame_seq: u32,
body: Vec<u8>,
peer_bytes: &mut ByteBudget,
) -> Applied {
if frame_seq < self.next_seq {
return Applied::Duplicate;
}
if frame_seq > self.next_seq {
return self.park(frame_seq, body, peer_bytes);
}
let class = classify(&body);
if let Err(fault) = self.admit(class) {
return fault_reason(&fault);
}
if let Err(fault) = self.deliver(body) {
return fault_reason(&fault);
}
self.next_seq = self.next_seq.saturating_add(1);
self.release_hold(peer_bytes)
}
pub(super) fn apply_close(&mut self, frame_seq: u32, reason: CloseReason) -> bool {
if frame_seq > self.next_seq {
self.pending_close = Some((frame_seq, reason));
return false;
}
true
}
pub(super) fn due_close(&mut self) -> Option<CloseReason> {
let (seq, reason) = self.pending_close?;
(seq <= self.next_seq).then(|| {
self.pending_close = None;
reason
})
}
pub(super) fn reconcile(&mut self) {
let in_channel = self.frame_tx.len() as u32;
let resident = in_channel.saturating_add(self.hold.len() as u32);
let drained = self.account.buffered().saturating_sub(resident);
if drained == 0 {
return;
}
for _ in 0..drained {
let Some(size) = self.sizes.pop_front() else {
break;
};
self.buffered_bytes = self.buffered_bytes.saturating_sub(u64::from(size));
}
self.account.release(drained);
}
pub(super) fn take_grant(&mut self) -> Option<u32> {
if self.buffered_bytes >= self.byte_watermark {
return None;
}
self.account.take_pending_grant()
}
pub(super) fn inject_dropped(&mut self) -> bool {
if self.account.admit(CreditClass::Terminal).is_err() {
return false;
}
self.frame_tx.try_send(cached_dropped().clone()).is_ok()
}
pub(super) fn hold_bytes_used(&self) -> u64 {
self.hold_bytes.used()
}
fn park(&mut self, frame_seq: u32, body: Vec<u8>, peer_bytes: &mut ByteBudget) -> Applied {
let class = classify(&body);
if let Err(fault) = self.admit(class) {
return fault_reason(&fault);
}
if try_reserve_pair(peer_bytes, &mut self.hold_bytes, body.len()).is_err() {
return Applied::Fault(CloseReason::ProtocolError);
}
self.hold.insert(frame_seq, body);
Applied::Held
}
fn release_hold(&mut self, peer_bytes: &mut ByteBudget) -> Applied {
while let Some(body) = self.hold.remove(&self.next_seq) {
let len = body.len();
if let Err(fault) = self.deliver(body) {
super::super::flow_control::release_pair(peer_bytes, &mut self.hold_bytes, len);
return fault_reason(&fault);
}
super::super::flow_control::release_pair(peer_bytes, &mut self.hold_bytes, len);
self.next_seq = self.next_seq.saturating_add(1);
}
Applied::Delivered
}
fn admit(&mut self, class: CreditClass) -> Result<(), DeliverFault> {
self.account
.admit(class)
.map_err(|_| DeliverFault::Overspend)
}
fn deliver(&mut self, body: Vec<u8>) -> Result<(), DeliverFault> {
let len = body.len() as u32;
match self.frame_tx.try_send(body) {
Ok(()) => {
self.sizes.push_back(len);
self.buffered_bytes = self.buffered_bytes.saturating_add(u64::from(len));
Ok(())
}
Err(flume::TrySendError::Full(_)) => Err(DeliverFault::ReaderStall),
Err(flume::TrySendError::Disconnected(_)) => Err(DeliverFault::ConsumerGone),
}
}
}
fn classify(body: &[u8]) -> CreditClass {
CreditClass::of(RecordType::Data, is_terminal_sentinel(body))
}
fn fault_reason(fault: &DeliverFault) -> Applied {
match fault {
DeliverFault::Overspend => Applied::Fault(CloseReason::ProtocolError),
DeliverFault::ReaderStall => Applied::ReaderStall,
DeliverFault::ConsumerGone => Applied::Fault(CloseReason::UnknownSlot),
}
}
pub(super) fn heartbeat_frame() -> Vec<u8> {
cached_heartbeat().clone()
}