use crate::error::Result;
use crate::offset_store::OffsetStore;
use crate::position::RecordPosition;
use crate::record::Record;
use crate::topic::{Topic, Wakeup};
use std::sync::Arc;
pub struct Consumer {
topic: Arc<Topic>,
cursors: Vec<u64>,
scan_start: usize,
wake: Arc<Wakeup>,
store: Option<OffsetStore>,
}
impl Consumer {
pub fn from_topic(topic: Arc<Topic>) -> Self {
Self::from_topic_at(topic, 0)
}
pub fn from_topic_at(topic: Arc<Topic>, start_offset: u64) -> Self {
let n = topic.partition_count() as usize;
let wake = topic.wake_handle();
Self {
topic,
cursors: vec![start_offset; n],
scan_start: 0,
wake,
store: None,
}
}
pub(crate) fn from_topic_with_group(topic: Arc<Topic>, store: OffsetStore) -> Self {
let wake = topic.wake_handle();
let cursors: Vec<u64> = topic
.partitions()
.iter()
.enumerate()
.map(|(p, partition)| {
let committed = store.committed().get(p).copied().unwrap_or(0);
committed.min(partition.high_water_mark())
})
.collect();
Self {
topic,
cursors,
scan_start: 0,
wake,
store: Some(store),
}
}
pub fn partition_count(&self) -> u32 {
self.cursors.len() as u32
}
pub fn position(&self, partition: u32) -> u64 {
self.cursors.get(partition as usize).copied().unwrap_or(0)
}
pub fn group(&self) -> Option<&str> {
self.store.as_ref().map(|s| s.group())
}
pub fn committed(&self, partition: u32) -> Option<u64> {
self.store
.as_ref()
.and_then(|s| s.committed().get(partition as usize).copied())
}
pub async fn commit(&mut self) -> Result<()> {
if self.store.is_some() {
let offsets = self.cursors.clone();
self.store
.as_mut()
.expect("store is Some")
.commit(&offsets)
.await?;
}
Ok(())
}
pub fn seek_all(&mut self, offset: u64) {
for cursor in &mut self.cursors {
*cursor = offset;
}
}
pub fn seek(&mut self, partition: u32, offset: u64) {
if let Some(cursor) = self.cursors.get_mut(partition as usize) {
*cursor = offset;
}
}
pub async fn next_record(&mut self) -> Result<Record> {
self.next_with_position().await.map(|(_, record)| record)
}
pub async fn next_with_position(&mut self) -> Result<(RecordPosition, Record)> {
loop {
if let Some(item) = self.try_take_next().await? {
return Ok(item);
}
let wake = self.wake.clone();
wake.mark_parked();
let notified = wake.notified();
tokio::pin!(notified);
notified.as_mut().enable();
match self.try_take_next().await {
Ok(Some(item)) => {
wake.unmark_parked();
return Ok(item);
}
Ok(None) => {}
Err(e) => {
wake.unmark_parked();
return Err(e);
}
}
notified.await;
wake.unmark_parked();
}
}
async fn try_take_next(&mut self) -> Result<Option<(RecordPosition, Record)>> {
let n = self.cursors.len();
for i in 0..n {
let p = (self.scan_start + i) % n;
let partition = self
.topic
.partition(p as u32)
.expect("scan index is always < partition_count");
if let Some(record) = partition.read_record_at(self.cursors[p]).await? {
let position = RecordPosition::new(p as u32, self.cursors[p]);
self.cursors[p] += 1;
self.scan_start = (p + 1) % n;
return Ok(Some((position, record)));
}
}
Ok(None)
}
}