use krabka_ids::LeaderEpoch;
use crate::{consumer::Consumer, error::ConsumerError, poll, position};
impl Consumer {
#[tracing::instrument(
name = "consumer.seek",
level = "debug",
skip_all,
fields(group_id = %self.group_id, topic = tracing::field::Empty, partition, offset),
err(level = "debug")
)]
pub async fn seek(
&self,
topic: impl Into<String>,
partition: i32,
offset: i64,
) -> Result<(), ConsumerError> {
self.seek_to_position(topic.into(), partition, offset, None)
.await
}
#[tracing::instrument(
name = "consumer.seek_with_leader_epoch",
level = "debug",
skip_all,
fields(group_id = %self.group_id, topic = tracing::field::Empty, partition, offset, leader_epoch),
err(level = "debug")
)]
pub async fn seek_with_leader_epoch(
&self,
topic: impl Into<String>,
partition: i32,
offset: i64,
leader_epoch: i32,
) -> Result<(), ConsumerError> {
self.seek_to_position(topic.into(), partition, offset, Some(leader_epoch))
.await
}
pub async fn seek_to_beginning(
&self,
partitions: &[(String, i32)],
) -> Result<(), ConsumerError> {
self.request_offset_reset(partitions, poll::BEGINNING_SENTINEL)
.await
}
pub async fn seek_to_end(&self, partitions: &[(String, i32)]) -> Result<(), ConsumerError> {
self.request_offset_reset(partitions, poll::END_SENTINEL)
.await
}
async fn request_offset_reset(
&self,
partitions: &[(String, i32)],
sentinel: i64,
) -> Result<(), ConsumerError> {
let assigned = self.assigned.lock().await;
let targets = if partitions.is_empty() {
assigned.clone()
} else {
if let Some((topic, partition)) = partitions
.iter()
.find(|partition| !assigned.contains(*partition))
{
return Err(ConsumerError::NoCurrentAssignment {
topic: topic.clone(),
partition: *partition,
});
}
partitions.to_vec()
};
let mut offsets = self.next_offsets.lock().await;
let mut positions = self.positions.lock().await;
for key in &targets {
offsets.insert(key.clone(), sentinel);
seek_position(positions.entry(key.clone()).or_default(), None);
}
drop(positions);
drop(offsets);
drop(assigned);
Ok(())
}
async fn seek_to_position(
&self,
topic: String,
partition: i32,
offset: i64,
leader_epoch: Option<i32>,
) -> Result<(), ConsumerError> {
if offset < 0 {
return Err(ConsumerError::InvalidOffset(offset));
}
tracing::Span::current().record("topic", tracing::field::display(&topic));
let key = (topic, partition);
let assigned = self.assigned.lock().await;
if !assigned.contains(&key) {
let (topic, partition) = key;
return Err(ConsumerError::NoCurrentAssignment { topic, partition });
}
let mut offsets = self.next_offsets.lock().await;
let mut positions = self.positions.lock().await;
offsets.insert(key.clone(), offset);
seek_position(positions.entry(key.clone()).or_default(), leader_epoch);
drop(positions);
drop(offsets);
drop(assigned);
Ok(())
}
}
fn seek_position(position: &mut position::PartitionPosition, leader_epoch: Option<i32>) {
position.offset_epoch = LeaderEpoch(leader_epoch.unwrap_or(-1));
position.awaiting_validation =
position.offset_epoch.is_known() && position.leader_epoch.is_known();
}
#[cfg(test)]
mod tests {
use krabka_ids::LeaderEpoch;
use super::{position::PartitionPosition, seek_position};
#[test]
fn seek_position_follows_kafkas_validate_position() {
for (name, current_leader_epoch, awaiting, seek_epoch, expected) in [
("no epoch", 5, true, None, (-1, false)),
("epoch, leader known", 5, false, Some(4), (4, true)),
("epoch, leader unknown", -1, false, Some(4), (4, false)),
] {
let mut position = PartitionPosition {
offset_epoch: LeaderEpoch(3),
leader_id: 1,
leader_epoch: LeaderEpoch(current_leader_epoch),
awaiting_validation: awaiting,
};
seek_position(&mut position, seek_epoch);
assert2::assert!(
position
== PartitionPosition {
offset_epoch: LeaderEpoch(expected.0),
leader_id: 1,
leader_epoch: LeaderEpoch(current_leader_epoch),
awaiting_validation: expected.1,
},
"{name}"
);
}
}
}