use std::any::type_name;
use std::fmt;
use std::marker::PhantomData;
#[cfg(feature = "testing")]
use std::sync::Arc;
use tracing::{error, warn};
use crate::{
BatchSubscriber, BuildBatchContext, CarriesBatch, ConnectedBroker, IncomingMessage,
SubscriptionSource,
};
use super::super::context::Context;
use super::super::dispatch::{Slot, settle_outcome};
use super::super::failure::FailurePolicy;
#[cfg(feature = "otel")]
use super::record_batch_size;
use super::{
BatchHandler, LentValues, SliceHandler, TakenBatch, rejection, reported, settle_lent_batch,
};
#[diagnostic::on_unimplemented(
message = "the subscription `{Self}` does not lend batches of `{T}`",
label = "a `&[{T}]` handler reads the values this subscription's batches hold",
note = "mount it on a subscription whose batches implement `CarriesBatch<{T}>`"
)]
pub trait SourceCarriesBatch<T, Conn> {
type Batch;
fn lend(batch: &Self::Batch) -> &[T];
}
#[diagnostic::do_not_recommend]
impl<T, Conn, Src> SourceCarriesBatch<T, Conn> for Src
where
Conn: ConnectedBroker,
Src: SubscriptionSource<Conn>,
Src::Subscriber: BatchSubscriber,
<Src::Subscriber as BatchSubscriber>::Batch: CarriesBatch<T>,
{
type Batch = <Src::Subscriber as BatchSubscriber>::Batch;
fn lend(batch: &Self::Batch) -> &[T] {
batch.carried()
}
}
type Lane<Src, Conn, T> = PhantomData<fn() -> (Src, Conn, T)>;
pub(crate) struct Lent<Src, Conn, Batch, T> {
batch: Batch,
recorded: LentValues,
_lane: Lane<Src, Conn, T>,
}
impl<Src, Conn, Batch, T, C> TakenBatch<C> for Lent<Src, Conn, Batch, T>
where
Src: SourceCarriesBatch<T, Conn, Batch = Batch>,
Batch: Send,
T: Clone + Send + Sync + 'static,
C: BuildBatchContext<Batch>,
{
fn context(&self) -> Option<C> {
Some(C::build(&self.batch))
}
#[cfg(feature = "testing")]
fn unsettled(&mut self) -> Vec<crate::testing::coordinator::Delivered> {
self.recorded = Src::lend(&self.batch)
.iter()
.map(|value| Arc::new(value.clone()) as crate::testing::coordinator::RecordedValue)
.collect();
self.recorded
.iter()
.map(|value| crate::testing::coordinator::Delivered {
raw: bytes::Bytes::new(),
value: Some(Arc::clone(value)),
settle: None,
})
.collect()
}
}
pub struct CarriedBatch<Src, Conn, T, Inner> {
inner: Inner,
decode: FailurePolicy,
_lane: Lane<Src, Conn, T>,
}
impl<Src, Conn, T, Inner> CarriedBatch<Src, Conn, T, Inner> {
#[must_use]
pub(crate) fn over(inner: Inner) -> Self {
Self {
inner,
decode: FailurePolicy::Drop,
_lane: PhantomData,
}
}
#[must_use]
pub(crate) fn with_decode(mut self, decode: FailurePolicy) -> Self {
self.decode = decode;
self
}
}
impl<Src, Conn, T, Inner> fmt::Debug for CarriedBatch<Src, Conn, T, Inner> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CarriedBatch")
.field("decode", &self.decode)
.finish_non_exhaustive()
}
}
impl<Src, Conn, Batch, M, T, Inner, C, S> BatchHandler<Batch, C, S>
for CarriedBatch<Src, Conn, T, Inner>
where
Src: SourceCarriesBatch<T, Conn, Batch = Batch>,
Batch: IntoIterator<Item = M> + Send,
M: IncomingMessage,
T: Clone + Send + Sync + 'static,
Inner: SliceHandler<T, C, S>,
C: BuildBatchContext<Batch> + BuildBatchContext<M> + Send + Sync + 'static,
S: Send + Sync,
{
type Scratch = ();
type Taken = Lent<Src, Conn, Batch, T>;
fn take(batch: Batch) -> Self::Taken {
Lent {
batch,
recorded: LentValues::default(),
_lane: PhantomData,
}
}
async fn handle_batch(
&self,
taken: Self::Taken,
_scratch: &mut (),
ctx: &mut Context<'_, C, S>,
) {
let subscription = ctx.subscription();
let delivery = ctx.delivery();
let Lent {
batch, recorded, ..
} = taken;
let values = Src::lend(&batch);
let lent = values.len();
let result = if lent == 0 {
None
} else {
#[cfg(feature = "otel")]
record_batch_size(subscription, lent);
Some(self.inner.handle_slice(values, ctx).await)
};
let mut deliveries: Vec<M> = batch.into_iter().collect();
if deliveries.len() < lent {
error!(
target: "ruststream::dispatch",
subscription = %subscription,
message_type = type_name::<T>(),
lent,
delivered = deliveries.len(),
"the batch lent more values than it delivered; the values past its last delivery \
have nothing to settle",
);
}
let gone = if deliveries.len() > lent {
deliveries.split_off(lent)
} else {
Vec::new()
};
for msg in gone {
let outcome = reported(&msg, type_name::<T>(), self.decode, ctx).unwrap_or_else(|| {
warn!(
target: "ruststream::dispatch",
subscription = %subscription,
message_type = type_name::<T>(),
"the delivery carries no value",
);
rejection(
&format_args!("the delivery carries no `{}`", type_name::<T>()),
"batch delivery carries no value",
self.decode,
ctx,
)
});
settle_outcome(
&mut Slot::new(msg),
outcome,
subscription,
delivery,
<C as BuildBatchContext<M>>::build as fn(&M) -> C,
)
.await;
}
if let Some(result) = result {
settle_lent_batch(deliveries, result, subscription, delivery, recorded).await;
}
}
}