use std::sync::Arc;
use super::*;
impl Batcher {
pub(super) async fn on_frame(&mut self, index: u32, bytes: Vec<u8>) {
let Some(slot) = self.slots.get_mut(index) else {
return;
};
if slot.must_withhold(CreditClass::Data) {
let terminal = is_terminal_sentinel(&bytes);
if terminal
&& slot.withheld.is_empty()
&& !slot.is_fenced()
&& slot.credit.can_spend(CreditClass::Terminal)
{
self.emit_data(index, bytes, true).await;
return;
}
let starved = slot.credit.data_available() == 0;
match slot.withheld.push(bytes) {
Ok(()) => {
if let Some(metrics) = &self.metrics {
metrics.withheld_records_delta(1);
if starved && slot.note_starved() {
metrics.credit_exhausted();
}
}
}
Err(error) => self.overflow_kill(index, error).await,
}
return;
}
let terminal = is_terminal_sentinel(&bytes);
self.emit_data(index, bytes, terminal).await;
}
async fn overflow_kill(&mut self, index: u32, error: slot_stream::WithheldOverflow) {
let Some(slot) = self.slots.get_mut(index) else {
return;
};
let id = slot.id;
let seq = slot.take_seq();
let discarded = slot.withheld.len();
slot.withheld.clear();
tracing::warn!(
slot = ?id,
%error,
discarded,
"messenger mux: producer outran a starved slot's byte cap; closing the slot"
);
if let Some(metrics) = &self.metrics {
metrics.records_dropped(MuxDropReason::WithheldOverflow, discarded as u64 + 1);
metrics.withheld_records_delta(-(discarded as i64));
}
self.push_close(id, seq, CloseReason::PeerGone).await;
self.close_local(index);
}
async fn push_close(&mut self, id: SlotId, seq: u32, reason: CloseReason) {
let needed = record_encoded_len(1).unwrap_or(usize::MAX);
self.ensure_batch();
if !self.fits(needed, 1) {
self.flush().await;
self.ensure_batch();
}
if let Some(encoder) = self.writer.encoder() {
let _ = encoder.push_close_slot(id, seq, reason);
self.gate.stage_urgent(1);
}
}
pub(super) async fn on_inlet_closed(&mut self, index: u32) {
let Some(slot) = self.slots.get_mut(index) else {
return;
};
slot.inlet_closed = true;
if !slot.withheld.is_empty() {
return;
}
self.finish_inlet_close(index).await;
}
async fn finish_inlet_close(&mut self, index: u32) {
let Some(slot) = self.slots.get_mut(index) else {
return;
};
let id = slot.id;
let seq = slot.take_seq();
self.push_close(id, seq, CloseReason::PeerGone).await;
self.close_local(index);
}
async fn emit_data(&mut self, index: u32, bytes: Vec<u8>, terminal: bool) {
let record_len = record_encoded_len(bytes.len()).unwrap_or(usize::MAX);
let close_len = record_encoded_len(1).unwrap_or(usize::MAX);
let needed = if terminal {
record_len.saturating_add(close_len)
} else {
record_len
};
let records = if terminal { 2 } else { 1 };
let mut cap = self.ensure_batch();
if BATCH_HEADER_LEN.saturating_add(needed) > cap {
self.flush().await;
self.send_singleton(index, bytes, terminal).await;
return;
}
if !self.fits(needed, records) {
self.flush().await;
cap = self.ensure_batch();
if BATCH_HEADER_LEN.saturating_add(needed) > cap {
self.send_singleton(index, bytes, terminal).await;
return;
}
}
let class = if terminal {
CreditClass::Terminal
} else {
CreditClass::Data
};
let Some(slot) = self.slots.get_mut(index) else {
return;
};
if slot.credit.try_spend(class).is_err() {
return;
}
let id = slot.id;
let seq = slot.take_seq();
let close_seq = terminal.then(|| slot.take_seq());
if let Some(encoder) = self.writer.encoder() {
if let Err(error) = encoder.push_data(id, seq, &bytes) {
tracing::error!(slot = ?id, %error, "messenger mux: dropping unencodable record");
return;
}
match close_seq {
Some(close_seq) => {
let _ = encoder.push_close_slot(id, close_seq, CloseReason::TerminalSent);
self.gate.stage_urgent(2);
}
None => self.gate.stage(1),
}
}
if terminal {
self.close_local(index);
}
}
async fn send_singleton(&mut self, index: u32, bytes: Vec<u8>, terminal: bool) {
let class = if terminal {
CreditClass::Terminal
} else {
CreditClass::Data
};
let Some(slot) = self.slots.get_mut(index) else {
return;
};
if slot.credit.try_spend(class).is_err() {
return;
}
let id = slot.id;
let seq = slot.take_seq();
let close_seq = terminal.then(|| slot.take_seq());
let fire = self.writer.dispatch_singleton(|encoder| {
encoder.push_data(id, seq, &bytes)?;
match close_seq {
Some(close_seq) => {
encoder.push_close_slot(id, close_seq, CloseReason::TerminalSent)
}
None => Ok(()),
}
});
let Some(fire) = fire else {
self.epoch_death();
return;
};
if terminal {
self.close_local(index);
} else if let Some(slot) = self.slots.get_mut(index) {
slot.fence();
}
let control = Arc::clone(&self.control);
tokio::spawn(async move {
control.singleton_resolved(id, fire.await.is_ok());
});
}
pub(super) async fn release_withheld(&mut self, index: u32) {
loop {
let next = {
let Some(slot) = self.slots.get_mut(index) else {
return;
};
if slot.is_fenced() {
return;
}
match slot.withheld.front() {
Some(front) => {
let terminal = is_terminal_sentinel(front);
let class = if terminal {
CreditClass::Terminal
} else {
CreditClass::Data
};
if !slot.credit.can_spend(class) {
return;
}
let popped = slot.withheld.pop();
if popped.is_some()
&& let Some(metrics) = &self.metrics
{
metrics.withheld_records_delta(-1);
}
popped.map(|bytes| (bytes, terminal))
}
None => {
slot.note_flowing();
None
}
}
};
match next {
Some((bytes, terminal)) => self.emit_data(index, bytes, terminal).await,
None => {
if self
.slots
.get_mut(index)
.is_some_and(|slot| slot.inlet_closed)
{
self.finish_inlet_close(index).await;
}
return;
}
}
}
}
}