use std::collections::HashSet;
use std::sync::Arc;
use cheetah_string::CheetahString;
use dashmap::DashMap;
use rocketmq_common::common::consumer::consume_from_where::ConsumeFromWhere;
use rocketmq_common::common::message::message_queue::MessageQueue;
use rocketmq_common::common::mix_all;
use rocketmq_remoting::code::response_code::ResponseCode;
use rocketmq_remoting::protocol::heartbeat::consume_type::ConsumeType;
use rocketmq_remoting::protocol::heartbeat::subscription_data::SubscriptionData;
use rocketmq_rust::ArcMut;
use rocketmq_rust::WeakArcMut;
use tracing::error;
use tracing::info;
use tracing::warn;
use crate::consumer::consumer_impl::pop_process_queue::PopProcessQueue;
use crate::consumer::consumer_impl::pop_request::PopRequest;
use crate::consumer::consumer_impl::process_queue::ProcessQueue;
use crate::consumer::consumer_impl::pull_request::PullRequest;
use crate::consumer::consumer_impl::re_balance::rebalance_impl::RebalanceImpl;
use crate::consumer::consumer_impl::re_balance::Rebalance;
use crate::consumer::default_mq_push_consumer::ConsumerConfig;
use crate::consumer::store::offset_store::OffsetStore;
use crate::consumer::store::read_offset_type::ReadOffsetType;
use crate::factory::mq_client_instance::MQClientInstance;
pub struct RebalanceLitePullImpl {
pub(crate) rebalance_impl_inner: RebalanceImpl<RebalanceLitePullImpl>,
pub(crate) consumer_config: ArcMut<ConsumerConfig>,
pub(crate) offset_store: Option<ArcMut<OffsetStore>>,
}
impl RebalanceLitePullImpl {
pub fn new(consumer_config: ArcMut<ConsumerConfig>) -> Self {
Self {
consumer_config,
rebalance_impl_inner: RebalanceImpl::new(None, None, None, None),
offset_store: None,
}
}
}
impl RebalanceLitePullImpl {
pub fn get_subscription_inner(&self) -> Arc<DashMap<CheetahString, SubscriptionData>> {
self.rebalance_impl_inner.subscription_inner.clone()
}
pub fn set_consumer_group(&mut self, consumer_group: CheetahString) {
self.rebalance_impl_inner.consumer_group = Some(consumer_group);
}
pub fn set_message_model(
&mut self,
message_model: rocketmq_remoting::protocol::heartbeat::message_model::MessageModel,
) {
self.rebalance_impl_inner.message_model = Some(message_model);
}
pub fn set_allocate_message_queue_strategy(
&mut self,
strategy: Arc<dyn crate::consumer::allocate_message_queue_strategy::AllocateMessageQueueStrategy>,
) {
self.rebalance_impl_inner.allocate_message_queue_strategy = Some(strategy);
}
pub fn set_mq_client_factory(&mut self, client_instance: ArcMut<MQClientInstance>) {
self.rebalance_impl_inner.client_instance = Some(client_instance);
}
pub fn set_rebalance_impl(&mut self, rebalance_impl: WeakArcMut<RebalanceLitePullImpl>) {
self.rebalance_impl_inner.sub_rebalance_impl = Some(rebalance_impl);
}
#[inline]
pub fn put_subscription_data(&mut self, topic: CheetahString, subscription_data: SubscriptionData) {
self.rebalance_impl_inner
.subscription_inner
.insert(topic, subscription_data);
}
pub fn set_offset_store(&mut self, offset_store: ArcMut<OffsetStore>) {
self.offset_store = Some(offset_store);
}
}
impl Rebalance for RebalanceLitePullImpl {
async fn message_queue_changed(
&mut self,
topic: &str,
mq_all: &HashSet<MessageQueue>,
mq_divided: &HashSet<MessageQueue>,
) {
let listener = self.consumer_config.message_queue_listener.clone();
if let Some(listener) = listener {
if let Err(err) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
listener.message_queue_changed(topic, mq_all, mq_divided);
})) {
error!("messageQueueChanged listener panicked for topic={}: {:?}", topic, err);
}
}
}
async fn remove_unnecessary_message_queue(&mut self, mq: &MessageQueue, _pq: &ProcessQueue) -> bool {
let Some(ref mut offset_store) = self.offset_store else {
warn!("Offset store not initialised; skipping persist/remove for {}", mq);
return true;
};
offset_store.persist(mq).await;
offset_store.remove_offset(mq).await;
true
}
fn consume_type(&self) -> ConsumeType {
ConsumeType::ConsumeActively
}
async fn remove_dirty_offset(&mut self, mq: &MessageQueue) {
let Some(ref mut offset_store) = self.offset_store else {
warn!("Offset store not initialised; cannot remove dirty offset for {}", mq);
return;
};
offset_store.remove_offset(mq).await;
}
#[allow(deprecated)]
async fn compute_pull_from_where_with_exception(
&mut self,
mq: &MessageQueue,
) -> rocketmq_error::RocketMQResult<i64> {
let consume_from_where = self.consumer_config.consume_from_where;
let offset_store = self.offset_store.as_ref().ok_or_else(|| {
error!("Offset store not initialised for mq: {}", mq);
mq_client_err!(
ResponseCode::SystemError as i32,
format!("Offset store not initialised for mq: {}", mq)
)
})?;
let last_offset = offset_store.read_offset(mq, ReadOffsetType::MemoryFirstThenStore).await;
let result = match consume_from_where {
ConsumeFromWhere::ConsumeFromLastOffset
| ConsumeFromWhere::ConsumeFromLastOffsetAndFromMinWhenBootFirst
| ConsumeFromWhere::ConsumeFromMinOffset
| ConsumeFromWhere::ConsumeFromMaxOffset => {
if last_offset >= 0 {
last_offset
} else if last_offset == -1 {
if mq.topic_str().starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX) {
0
} else {
let client = self.rebalance_impl_inner.client_instance.as_mut().ok_or_else(|| {
error!("Client instance not initialised for mq: {}", mq);
mq_client_err!(
ResponseCode::SystemError as i32,
format!("Client instance not initialised for mq: {}", mq)
)
})?;
client.mq_admin_impl.max_offset(mq).await?
}
} else {
-1
}
}
ConsumeFromWhere::ConsumeFromFirstOffset => {
if last_offset >= 0 {
last_offset
} else if last_offset == -1 {
0
} else {
-1
}
}
ConsumeFromWhere::ConsumeFromTimestamp => {
if last_offset >= 0 {
last_offset
} else if last_offset == -1 {
if mq.topic_str().starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX) {
let client = self.rebalance_impl_inner.client_instance.as_mut().ok_or_else(|| {
error!("Client instance not initialised for mq: {}", mq);
mq_client_err!(
ResponseCode::SystemError as i32,
format!("Client instance not initialised for mq: {}", mq)
)
})?;
client.mq_admin_impl.max_offset(mq).await?
} else {
let timestamp = super::parse_consume_timestamp_millis(
self.consumer_config.consume_timestamp.as_deref(),
mq,
)?;
let client = self.rebalance_impl_inner.client_instance.as_mut().ok_or_else(|| {
error!("Client instance not initialised for mq: {}", mq);
mq_client_err!(
ResponseCode::SystemError as i32,
format!("Client instance not initialised for mq: {}", mq)
)
})?;
client.mq_admin_impl.search_offset(mq, timestamp).await?
}
} else {
-1
}
}
};
Ok(result)
}
async fn compute_pull_from_where(&mut self, mq: &MessageQueue) -> i64 {
self.compute_pull_from_where_with_exception(mq)
.await
.unwrap_or_else(|e| {
warn!("Compute consume offset exception, mq={}, err={:?}", mq, e);
-1
})
}
fn get_consume_init_mode(&self) -> i32 {
warn!("get_consume_init_mode is not supported for LitePull consumers");
-1
}
async fn dispatch_pull_request(&self, _pull_request_list: Vec<PullRequest>, _delay: u64) {}
async fn dispatch_pop_pull_request(&self, _pop_request_list: Vec<PopRequest>, _delay: u64) {}
#[inline]
fn create_process_queue(&self) -> ProcessQueue {
ProcessQueue::new()
}
#[inline]
fn create_pop_process_queue(&self) -> PopProcessQueue {
PopProcessQueue::new()
}
async fn remove_process_queue(&mut self, mq: &MessageQueue) {
let mut process_queue_table = self.rebalance_impl_inner.process_queue_table.write().await;
let prev = process_queue_table.remove(mq);
drop(process_queue_table);
if let Some(pq) = prev {
let dropped = pq.is_dropped();
pq.set_dropped(true);
self.remove_unnecessary_message_queue(mq, &pq).await;
if let Some(ref consumer_group) = self.rebalance_impl_inner.consumer_group {
info!(
"Rebalance cleanup for {}, removed unnecessary mq: {}, dropped: {}",
consumer_group, mq, dropped
);
}
}
}
async fn unlock(&mut self, _mq: &MessageQueue, _oneway: bool) {}
async fn lock_all(&mut self) {}
async fn unlock_all(&mut self, _oneway: bool) {}
async fn do_rebalance(&mut self, is_order: bool) -> bool {
self.rebalance_impl_inner.do_rebalance(is_order).await
}
fn client_rebalance(&mut self, _topic: &str) -> bool {
true
}
fn destroy(&mut self) {
info!(
"Destroying RebalanceLitePullImpl for consumer group: {:?}",
self.rebalance_impl_inner.consumer_group
);
if let Ok(mut table) = self.rebalance_impl_inner.process_queue_table.try_write() {
for pq in table.values() {
pq.set_dropped(true);
}
table.clear();
} else {
warn!("destroy: could not acquire write lock on process_queue_table; queues may not be dropped cleanly");
}
if let Ok(mut pop_table) = self.rebalance_impl_inner.pop_process_queue_table.try_write() {
for pq in pop_table.values() {
pq.set_dropped(true);
}
pop_table.clear();
} else {
warn!(
"destroy: could not acquire write lock on pop_process_queue_table; queues may not be dropped cleanly"
);
}
}
}