use std::fmt;
use std::future::{Future as _, poll_fn};
use std::pin::pin;
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use futures::StreamExt as _;
use rdkafka::consumer::Consumer as _;
use rdkafka::error::KafkaError;
use rdkafka::message::{
BorrowedHeaders, BorrowedMessage, Header, Headers as _, Message as _, OwnedHeaders,
OwnedMessage,
};
use ruststream::Str;
use yoke::{Yoke, Yokeable};
#[cfg(feature = "testing")]
use crate::in_process::{InProcessRecord, WireHeader};
use crate::seek::KafkaSeeker;
use crate::tracker::{CommitTracker, StoreTarget, TrackedConsumer};
pub(crate) type SharedConsumer = Arc<TrackedConsumer>;
#[derive(Debug)]
pub(crate) struct Shared {
pub(crate) tracker: Arc<CommitTracker>,
pub(crate) seeker: Arc<KafkaSeeker>,
topic: Str,
}
impl Shared {
pub(crate) const fn new(
tracker: Arc<CommitTracker>,
seeker: Arc<KafkaSeeker>,
topic: Str,
) -> Self {
Self {
tracker,
seeker,
topic,
}
}
pub(crate) fn topic<'a>(&'a self, own: Option<&'a Str>) -> &'a Str {
own.unwrap_or(&self.topic)
}
}
pub(crate) struct LiveShared {
pub(crate) consumer: SharedConsumer,
pub(crate) shared: Arc<Shared>,
}
pub(crate) type Cart = Arc<LiveShared>;
#[derive(Yokeable)]
pub(crate) struct Record<'a>(BorrowedMessage<'a>);
enum NotTaken {
Empty,
Failed(KafkaError),
}
pub(crate) enum HeldRecord {
Fetched(Yoke<Record<'static>, Cart>),
Copied {
cart: Cart,
message: Box<OwnedMessage>,
},
#[cfg(feature = "testing")]
InProcess {
shared: Arc<Shared>,
record: Box<InProcessRecord>,
},
}
#[cfg(not(feature = "testing"))]
const _: () = assert!(size_of::<HeldRecord>() == 3 * size_of::<usize>());
impl HeldRecord {
pub(crate) fn ready(cart: &Cart) -> Option<Result<Self, KafkaError>> {
let taken = Yoke::try_attach_to_cart(Arc::clone(cart), |cart| {
take_fetched(&cart.consumer)
.ok_or(NotTaken::Empty)?
.map(Record)
.map_err(NotTaken::Failed)
});
match taken {
Ok(yoke) => Some(Ok(Self::Fetched(yoke))),
Err(NotTaken::Empty) => None,
Err(NotTaken::Failed(err)) => Some(Err(err)),
}
}
pub(crate) async fn next(cart: &Cart) -> Result<Self, KafkaError> {
let mut waiter = pin!(cart.consumer.recv());
poll_fn(move |cx| {
if let Some(taken) = Self::ready(cart) {
return Poll::Ready(taken);
}
match waiter.as_mut().poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(message)) => Poll::Ready(Ok(Self::Copied {
cart: Arc::clone(cart),
message: Box::new(message.detach()),
})),
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
}
})
.await
}
pub(crate) fn store(
&self,
offset: i64,
position: i64,
mut target: StoreTarget<'_>,
) -> Result<(), KafkaError> {
match self {
Self::Fetched(yoke) if position == offset => yoke
.backing_cart()
.consumer
.store_offset_from_message(&yoke.get().0),
Self::Fetched(yoke) => yoke
.backing_cart()
.consumer
.store_offsets(target.list(position)?),
Self::Copied { cart, .. } => cart.consumer.store_offsets(target.list(position)?),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => {
record.store(target.topic(), target.partition(), position);
Ok(())
}
}
}
pub(crate) fn shared(&self) -> &Arc<Shared> {
match self {
Self::Fetched(yoke) => &yoke.backing_cart().shared,
Self::Copied { cart, .. } => &cart.shared,
#[cfg(feature = "testing")]
Self::InProcess { shared, .. } => shared,
}
}
pub(crate) fn topic(&self) -> &str {
match self {
Self::Fetched(yoke) => yoke.get().0.topic(),
Self::Copied { message, .. } => message.topic(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => record.topic(),
}
}
pub(crate) fn partition(&self) -> i32 {
match self {
Self::Fetched(yoke) => yoke.get().0.partition(),
Self::Copied { message, .. } => message.partition(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => record.partition(),
}
}
pub(crate) fn offset(&self) -> i64 {
match self {
Self::Fetched(yoke) => yoke.get().0.offset(),
Self::Copied { message, .. } => message.offset(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => record.offset(),
}
}
pub(crate) fn timestamp_millis(&self) -> Option<i64> {
match self {
Self::Fetched(yoke) => yoke.get().0.timestamp().to_millis(),
Self::Copied { message, .. } => message.timestamp().to_millis(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => Some(record.timestamp_millis()),
}
}
pub(crate) fn payload(&self) -> &[u8] {
match self {
Self::Fetched(yoke) => yoke.get().0.payload().unwrap_or_default(),
Self::Copied { message, .. } => message.payload().unwrap_or_default(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => record.payload(),
}
}
pub(crate) fn key(&self) -> Option<&[u8]> {
match self {
Self::Fetched(yoke) => yoke.get().0.key(),
Self::Copied { message, .. } => message.key(),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => record.key(),
}
}
pub(crate) fn headers(&self) -> Option<RecordHeaders<'_>> {
match self {
Self::Fetched(yoke) => yoke.get().0.headers().map(RecordHeaders::Fetched),
Self::Copied { message, .. } => message.headers().map(RecordHeaders::Copied),
#[cfg(feature = "testing")]
Self::InProcess { record, .. } => Some(RecordHeaders::InProcess(record.headers())),
}
}
}
#[inline]
fn take_fetched(consumer: &TrackedConsumer) -> Option<Result<BorrowedMessage<'_>, KafkaError>> {
match consumer
.stream()
.poll_next_unpin(&mut Context::from_waker(Waker::noop()))
{
Poll::Ready(taken) => taken,
Poll::Pending => None,
}
}
impl fmt::Debug for HeldRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HeldRecord")
.field("partition", &self.partition())
.field("offset", &self.offset())
.finish_non_exhaustive()
}
}
pub(crate) enum RecordHeaders<'a> {
Fetched(&'a BorrowedHeaders),
Copied(&'a OwnedHeaders),
#[cfg(feature = "testing")]
InProcess(&'a [WireHeader]),
}
impl RecordHeaders<'_> {
fn count(&self) -> usize {
match self {
Self::Fetched(headers) => headers.count(),
Self::Copied(headers) => headers.count(),
#[cfg(feature = "testing")]
Self::InProcess(headers) => headers.len(),
}
}
fn get(&self, index: usize) -> Header<'_, &[u8]> {
match self {
Self::Fetched(headers) => headers.get(index),
Self::Copied(headers) => headers.get(index),
#[cfg(feature = "testing")]
Self::InProcess(headers) => {
let (key, value) = &headers[index];
Header {
key,
value: value.as_deref(),
}
}
}
}
pub(crate) fn iter(&self) -> impl Iterator<Item = Header<'_, &[u8]>> {
(0..self.count()).map(|index| self.get(index))
}
}