use liminal::protocol::{
CausalContext, Frame, MessageEnvelope, SchemaId as ProtocolSchemaId, encoded_len,
};
use super::outbound::{OutboundError, OutboundWriter};
use super::state::ConnectionProcessState;
pub(super) const DELIVERY_SLICE_BUDGET: usize = 32;
const SERVER_ERROR_CODE: u16 = 0xFFFF;
pub(super) fn service_subscriptions(
state: &mut ConnectionProcessState,
outbound: &mut OutboundWriter,
budget: usize,
) -> Result<Vec<u64>, OutboundError> {
let ConnectionProcessState {
subscriptions,
delivery_seqs,
held_deliveries,
..
} = state;
let mut remaining = budget;
let mut shed = Vec::new();
for (subscription_id, subscription) in subscriptions.iter_mut() {
if remaining == 0 {
break;
}
let stream_id = subscription.stream_id();
let selected_schema = subscription.selected_schema();
if subscription.is_overflowed() {
let frame = Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(
"subscription shed: connection inbox byte budget or per-inbox fairness \
limit exceeded"
.to_owned(),
),
};
let needed = encoded_len(&frame).map_err(OutboundError::Encode)?;
if needed <= outbound.capacity() && !outbound.has_room(needed) {
continue;
}
outbound.enqueue_frame(&frame)?;
shed.push(*subscription_id);
continue;
}
while remaining > 0 {
let frame = if let Some(held) = held_deliveries.remove(subscription_id) {
held
} else {
let Some(envelope) = subscription.try_next() else {
break;
};
let sequence = delivery_seqs.entry(*subscription_id).or_insert(0);
*sequence = sequence.saturating_add(1);
build_deliver_frame(stream_id, *sequence, selected_schema, envelope)?
};
let needed = encoded_len(&frame).map_err(OutboundError::Encode)?;
if needed <= outbound.capacity() && !outbound.has_room(needed) {
held_deliveries.insert(*subscription_id, frame);
return Ok(shed);
}
outbound.enqueue_frame(&frame)?;
remaining -= 1;
}
}
Ok(shed)
}
fn build_deliver_frame(
stream_id: u32,
delivery_seq: u64,
selected_schema: ProtocolSchemaId,
envelope: liminal::envelope::Envelope,
) -> Result<Frame, OutboundError> {
let protocol_envelope = MessageEnvelope::new(
selected_schema,
CausalContext::independent(),
envelope.payload,
);
Frame::new_deliver(stream_id, delivery_seq, protocol_envelope).map_err(OutboundError::Encode)
}
#[cfg(test)]
#[path = "delivery_tests.rs"]
mod tests;