use std::collections::{BTreeMap, VecDeque};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI16, AtomicI32, AtomicU64, Ordering};
use std::time::Duration;
use ahash::AHashMap as HashMap;
use parking_lot::Mutex;
use tokio::sync::Notify;
use tokio::task::JoinHandle;
use tokio::time::Instant;
use super::acks::{AckBook, AckRange, AckType, Resolved};
use super::commit::{self, AcknowledgementCommitCallback};
use super::completed_fetch::CompletedFetch;
use super::config::{AcknowledgementMode, ShareConsumerConfig};
use super::membership::MemberState;
use super::request_manager;
use crate::consumer::{ConsumerRecord, TopicPartition};
use crate::error::{KrafkaError, Result};
use crate::metadata::ClusterMetadata;
use crate::network::ConnectionPool;
use crate::serdes::Deserializer;
use crate::{BrokerId, Offset, PartitionId};
const BACKOFF_INITIAL: Duration = Duration::from_millis(100);
const BACKOFF_MAX: Duration = Duration::from_secs(1);
#[derive(Debug, Clone, Copy)]
pub(crate) struct Backoff {
attempts: u32,
pub until: Instant,
}
impl Backoff {
pub(crate) fn after(previous: Option<Self>) -> Self {
let attempts = previous.map_or(1, |b| b.attempts.saturating_add(1));
let delay = BACKOFF_INITIAL
.saturating_mul(1u32 << (attempts - 1).min(16))
.min(BACKOFF_MAX);
Self {
attempts,
until: Instant::now() + delay,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Assigned {
pub partition: TopicPartition,
pub topic_id: [u8; 16],
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Acquisition {
pub node: BrokerId,
pub topic_id: [u8; 16],
}
#[derive(Debug, Default)]
pub(crate) struct State {
pub assigned: Vec<Assigned>,
pub unresolved: Vec<([u8; 16], Vec<PartitionId>)>,
pub buffer: VecDeque<CompletedFetch>,
pub outstanding: HashMap<TopicPartition, BTreeMap<Offset, Acquisition>>,
pub book: AckBook,
pub fetch_wanted: bool,
pub deferred_error: Option<KrafkaError>,
pub fatal: Option<KrafkaError>,
pub backoff: HashMap<TopicPartition, Backoff>,
pub closing: bool,
}
impl State {
pub(crate) fn buffers_node(&self, node: BrokerId) -> bool {
self.buffer
.iter()
.any(|f| f.node == node && !f.records.is_empty())
}
}
#[derive(Debug)]
pub(crate) struct NodeHandle {
pub notify: Arc<Notify>,
pub ack_version: Arc<AtomicI16>,
pub task: JoinHandle<()>,
}
pub(crate) struct Inner {
pub config: ShareConsumerConfig,
pub metadata: Arc<ClusterMetadata>,
pub pool: Arc<ConnectionPool>,
pub metrics: Arc<crate::metrics::ConsumerRecorder>,
pub metrics_source: Arc<crate::metrics::MetricsSource>,
pub telemetry: crate::telemetry::Telemetry,
pub key_deserializer: Option<Arc<dyn Deserializer>>,
pub value_deserializer: Option<Arc<dyn Deserializer>>,
pub callback: Option<AcknowledgementCommitCallback>,
pub member: Mutex<MemberState>,
pub state: Mutex<State>,
pub records_ready: Notify,
pub poll_lock: tokio::sync::Mutex<()>,
pub closed: AtomicBool,
pub shut_down: AtomicBool,
pub wakeup: AtomicBool,
pub session_generation: AtomicU64,
pub acquisition_lock_timeout_ms: AtomicI32,
pub nodes: Mutex<HashMap<BrokerId, NodeHandle>>,
pub heartbeat_task: Mutex<Option<JoinHandle<()>>>,
}
impl Drop for Inner {
fn drop(&mut self) {
if let Some(task) = self.heartbeat_task.lock().take() {
task.abort();
}
for (_, node) in self.nodes.lock().drain() {
node.task.abort();
}
if !self.closed.load(Ordering::Relaxed) && !std::thread::panicking() {
tracing::warn!(
"ShareConsumer dropped without close(); its acquired records stay locked until \
their acquisition locks expire and the group notices the member is gone"
);
}
}
}
impl Inner {
pub(crate) fn lock_timeout(&self) -> Duration {
let ms = self.acquisition_lock_timeout_ms.load(Ordering::Relaxed);
if ms > 0 {
Duration::from_millis(ms as u64)
} else {
Duration::from_secs(30)
}
}
pub(crate) fn wake_nodes(self: &Arc<Self>) {
let mut wanted: Vec<BrokerId> = {
let state = self.state.lock();
let mut nodes = state.book.nodes();
nodes.extend(state.assigned.iter().filter_map(|a| {
self.metadata
.leader(&a.partition.topic, a.partition.partition)
}));
nodes
};
wanted.sort_unstable();
wanted.dedup();
let mut nodes = self.nodes.lock();
if self.shut_down.load(Ordering::Acquire) {
return;
}
for node in wanted {
nodes
.entry(node)
.or_insert_with(|| request_manager::spawn(self, node));
}
for handle in nodes.values() {
handle.notify.notify_one();
}
}
pub(crate) fn wake_node(self: &Arc<Self>, node: BrokerId) {
let notify = self.nodes.lock().get(&node).map(|h| Arc::clone(&h.notify));
match notify {
Some(notify) => notify.notify_one(),
None => self.wake_nodes(),
}
}
pub(crate) fn report(&self, resolved: &[Resolved]) {
commit::report(self.callback.as_ref(), resolved);
}
pub(crate) fn accept_delivered(&self) -> bool {
if self.config.acknowledgement_mode != AcknowledgementMode::Implicit {
return false;
}
let mut state = self.state.lock();
let outstanding = std::mem::take(&mut state.outstanding);
let queued = !outstanding.is_empty();
for (partition, records) in outstanding {
for (offset, acquisition) in records {
state.book.add(
acquisition.node,
partition.clone(),
acquisition.topic_id,
AckRange::one(offset, AckType::Accept),
);
}
}
queued
}
pub(crate) fn acknowledge(
self: &Arc<Self>,
record: &ConsumerRecord,
kind: AckType,
) -> Result<()> {
if self.config.acknowledgement_mode != AcknowledgementMode::Explicit {
return Err(KrafkaError::illegal_state(
"acknowledging a record needs AcknowledgementMode::Explicit",
));
}
if self.closed.load(Ordering::Acquire) {
return Err(KrafkaError::closed("share consumer is closed"));
}
let partition = TopicPartition::new(&*record.topic, record.partition);
let node = {
let mut state = self.state.lock();
let acquisition = state
.outstanding
.get(&partition)
.and_then(|records| records.get(&record.offset))
.copied()
.ok_or_else(|| {
KrafkaError::illegal_state(format!(
"record {}-{}@{} is not awaiting acknowledgement",
record.topic, record.partition, record.offset
))
})?;
if kind == AckType::Renew {
let version = self
.nodes
.lock()
.get(&acquisition.node)
.map_or(-1, |h| h.ack_version.load(Ordering::Acquire));
if version < 2 {
return Err(KrafkaError::protocol_kind(
crate::error::ProtocolErrorKind::UnknownApiVersion,
format!(
"renewing an acquisition lock needs KIP-1222 (ShareAcknowledge v2, \
Kafka 4.2+); broker {} negotiated v{version}",
acquisition.node
),
));
}
} else if let Some(records) = state.outstanding.get_mut(&partition) {
records.remove(&record.offset);
if records.is_empty() {
state.outstanding.remove(&partition);
}
}
state.book.add(
acquisition.node,
partition,
acquisition.topic_id,
AckRange::one(record.offset, kind),
);
acquisition.node
};
self.wake_node(node);
Ok(())
}
pub(crate) fn drop_partition_state(&self, error: &KrafkaError) -> Vec<Resolved> {
let mut state = self.state.lock();
state.buffer.clear();
state.outstanding.clear();
state.backoff.clear();
state.deferred_error = None;
state.book.fail_pending(|_, _| true, |_| error.clone())
}
pub(crate) fn install_assignment(
&self,
assigned: Vec<Assigned>,
unresolved: Vec<([u8; 16], Vec<PartitionId>)>,
) -> bool {
let mut state = self.state.lock();
let revoked: Vec<TopicPartition> = state
.assigned
.iter()
.filter(|old| !assigned.iter().any(|new| new.partition == old.partition))
.map(|old| old.partition.clone())
.collect();
let changed = state.assigned != assigned || state.unresolved != unresolved;
let state = &mut *state;
for partition in &revoked {
let mut kept = VecDeque::with_capacity(state.buffer.len());
for fetch in state.buffer.drain(..) {
if fetch.topic == partition.topic && fetch.partition == partition.partition {
for record in &fetch.records {
state.book.add(
fetch.node,
partition.clone(),
fetch.topic_id,
AckRange::one(record.offset, AckType::Release),
);
}
} else {
kept.push_back(fetch);
}
}
state.buffer = kept;
state.outstanding.remove(partition);
state.backoff.remove(partition);
}
state.assigned = assigned;
state.unresolved = unresolved;
changed
}
pub(crate) fn take_records(
self: &Arc<Self>,
max: usize,
) -> Result<Option<Vec<ConsumerRecord>>> {
let candidates: Vec<ConsumerRecord> = {
let state = self.state.lock();
state
.buffer
.iter()
.flat_map(|f| f.records.iter())
.take(max)
.cloned()
.collect()
};
if candidates.is_empty() {
return Ok(None);
}
let mut decoded = Vec::with_capacity(candidates.len());
let mut failure = None;
for mut record in candidates {
match self.deserialize(&mut record) {
Ok(()) => decoded.push(record),
Err(error) => {
failure = Some((record, error));
break;
}
}
}
let mut delivered = Vec::with_capacity(decoded.len());
let mut released_node = None;
{
let mut state = self.state.lock();
for record in decoded {
if let Some(acquisition) = pop_front(&mut state, &record) {
state
.outstanding
.entry(TopicPartition::new(&*record.topic, record.partition))
.or_default()
.insert(record.offset, acquisition);
delivered.push(record);
}
}
if let Some((record, _)) = &failure
&& let Some(acquisition) = pop_front(&mut state, record)
{
state.book.add(
acquisition.node,
TopicPartition::new(&*record.topic, record.partition),
acquisition.topic_id,
AckRange::one(record.offset, AckType::Release),
);
released_node = Some(acquisition.node);
}
state.buffer.retain(|f| !f.records.is_empty());
if let Some((_, error)) = &failure
&& !delivered.is_empty()
{
state.deferred_error = Some(error.clone());
}
}
if let Some(node) = released_node {
self.wake_node(node);
}
if !delivered.is_empty() {
let bytes: u64 = delivered
.iter()
.map(|r| r.value.as_ref().map_or(0, |v| v.len() as u64))
.sum();
self.metrics.record_receive(delivered.len() as u64, bytes);
return Ok(Some(delivered));
}
match failure {
Some((_, error)) => Err(error),
None => Ok(None),
}
}
fn deserialize(&self, record: &mut ConsumerRecord) -> Result<()> {
let decode = |decoder: &Arc<dyn Deserializer>,
record: &ConsumerRecord,
payload: &bytes::Bytes,
is_key: bool| {
decoder
.deserialize(&record.topic, &record.headers, payload.clone(), is_key)
.map_err(|e| {
KrafkaError::record_deserialization(
&*record.topic,
record.partition,
record.offset,
if is_key { "key" } else { "value" },
e.to_string(),
)
})
};
if let (Some(decoder), Some(value)) = (&self.value_deserializer, record.value.as_ref()) {
record.value = Some(decode(decoder, record, value, false)?);
}
if let (Some(decoder), Some(key)) = (&self.key_deserializer, record.key.as_ref()) {
record.key = Some(decode(decoder, record, key, true)?);
}
Ok(())
}
}
fn pop_front(state: &mut State, record: &ConsumerRecord) -> Option<Acquisition> {
let fetch = state.buffer.iter_mut().find(|f| {
*f.topic == *record.topic
&& f.partition == record.partition
&& f.records.front().is_some_and(|r| r.offset == record.offset)
})?;
fetch.records.pop_front();
Some(Acquisition {
node: fetch.node,
topic_id: fetch.topic_id,
})
}