use std::collections::HashSet;
use std::sync::Arc;
use std::sync::Weak;
use arc_swap::ArcSwap;
use arc_swap::ArcSwapOption;
use cheetah_string::CheetahString;
use dashmap::DashMap;
use rocketmq_model::common::consumer::consume_from_where::ConsumeFromWhere;
use rocketmq_model::common::message::message_queue::MessageQueue;
use rocketmq_model::common::mix_all;
use rocketmq_protocol::code::response_code::ResponseCode;
use rocketmq_protocol::protocol::heartbeat::consume_type::ConsumeType;
use rocketmq_protocol::protocol::heartbeat::subscription_data::SubscriptionData;
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>,
consumer_config: ArcSwap<ConsumerConfig>,
pub(crate) offset_store: ArcSwapOption<OffsetStore>,
}
impl RebalanceLitePullImpl {
pub fn new(consumer_config: ConsumerConfig) -> Self {
Self {
consumer_config: ArcSwap::from_pointee(consumer_config),
rebalance_impl_inner: RebalanceImpl::new(None, None, None, None),
offset_store: ArcSwapOption::empty(),
}
}
fn update_consumer_config(&self, update: impl Fn(&mut ConsumerConfig)) {
self.consumer_config.rcu(|current| {
let mut next = (**current).clone();
update(&mut next);
Arc::new(next)
});
}
}
impl RebalanceLitePullImpl {
pub fn get_subscription_inner(&self) -> Arc<DashMap<CheetahString, SubscriptionData>> {
self.rebalance_impl_inner.subscription_inner.clone()
}
pub fn set_consumer_group(&self, consumer_group: CheetahString) {
self.rebalance_impl_inner.set_consumer_group(consumer_group.clone());
self.update_consumer_config(|config| config.consumer_group = consumer_group.clone());
}
pub fn set_message_model(
&self,
message_model: rocketmq_protocol::protocol::heartbeat::message_model::MessageModel,
) {
self.rebalance_impl_inner.set_message_model(message_model);
self.update_consumer_config(|config| config.message_model = message_model);
}
pub fn set_allocate_message_queue_strategy(
&self,
strategy: Arc<dyn crate::consumer::allocate_message_queue_strategy::AllocateMessageQueueStrategy>,
) {
self.rebalance_impl_inner
.set_allocate_message_queue_strategy(strategy.clone());
self.update_consumer_config(|config| config.allocate_message_queue_strategy = Some(strategy.clone()));
}
pub fn set_unit_mode(&self, unit_mode: bool) {
self.update_consumer_config(|config| config.unit_mode = unit_mode);
}
pub fn set_consume_from_where(&self, consume_from_where: ConsumeFromWhere) {
self.update_consumer_config(|config| config.consume_from_where = consume_from_where);
}
pub fn set_consume_timestamp(&self, consume_timestamp: Option<CheetahString>) {
self.update_consumer_config(|config| config.consume_timestamp = consume_timestamp.clone());
}
pub fn set_message_queue_listener(
&self,
listener: Option<Arc<dyn crate::consumer::message_queue_listener::MessageQueueListener>>,
) {
self.update_consumer_config(|config| config.message_queue_listener = listener.clone());
}
pub fn set_mq_client_factory(
&self,
consumer_group: CheetahString,
message_model: rocketmq_protocol::protocol::heartbeat::message_model::MessageModel,
strategy: Arc<dyn crate::consumer::allocate_message_queue_strategy::AllocateMessageQueueStrategy>,
client_instance: Arc<MQClientInstance>,
) {
self.rebalance_impl_inner.set_consumer_group(consumer_group.clone());
self.rebalance_impl_inner.set_message_model(message_model);
self.rebalance_impl_inner
.set_allocate_message_queue_strategy(strategy.clone());
self.rebalance_impl_inner.set_mq_client_factory(client_instance);
self.update_consumer_config(|config| {
config.consumer_group = consumer_group.clone();
config.message_model = message_model;
config.allocate_message_queue_strategy = Some(strategy.clone());
});
}
pub fn set_rebalance_impl(&self, rebalance_impl: Weak<RebalanceLitePullImpl>) {
let _ = self.rebalance_impl_inner.sub_rebalance_impl.set(rebalance_impl);
}
#[inline]
pub fn put_subscription_data(&self, topic: CheetahString, subscription_data: SubscriptionData) {
self.rebalance_impl_inner
.subscription_inner
.insert(topic, subscription_data);
}
pub fn set_offset_store(&self, offset_store: Option<Arc<OffsetStore>>) {
self.offset_store.store(offset_store);
}
}
impl Rebalance for RebalanceLitePullImpl {
async fn message_queue_changed(
&self,
topic: &str,
mq_all: &HashSet<MessageQueue>,
mq_divided: &HashSet<MessageQueue>,
) {
let listener = self.consumer_config.load().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(&self, mq: &MessageQueue, _pq: &ProcessQueue) -> bool {
let Some(offset_store) = self.offset_store.load_full() 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(&self, mq: &MessageQueue) {
let Some(offset_store) = self.offset_store.load_full() 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(&self, mq: &MessageQueue) -> crate::ClientResult<i64> {
let consumer_config = self.consumer_config.load_full();
let consume_from_where = consumer_config.consume_from_where;
let offset_store = self.offset_store.load_full().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.get_mq_client_factory().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.get_mq_client_factory().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(consumer_config.consume_timestamp.as_deref(), mq)?;
let client = self.rebalance_impl_inner.get_mq_client_factory().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(&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(&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(consumer_group) = self.rebalance_impl_inner.consumer_group() {
info!(
"Rebalance cleanup for {}, removed unnecessary mq: {}, dropped: {}",
consumer_group, mq, dropped
);
}
}
}
async fn unlock(&self, _mq: &MessageQueue, _oneway: bool) {}
async fn lock_all(&self) {}
async fn unlock_all(&self, _oneway: bool) {}
async fn do_rebalance(&self, is_order: bool) -> bool {
self.rebalance_impl_inner.do_rebalance(is_order).await
}
fn client_rebalance(&self, _topic: &str) -> bool {
true
}
fn destroy(&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"
);
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::RebalanceLitePullImpl;
use crate::consumer::default_mq_push_consumer::ConsumerConfig;
#[test]
fn standard_weak_self_reference_does_not_keep_rebalance_alive() {
let rebalance = Arc::new(RebalanceLitePullImpl::new(ConsumerConfig::default()));
let weak = Arc::downgrade(&rebalance);
rebalance.set_rebalance_impl(weak.clone());
assert!(weak.upgrade().is_some());
drop(rebalance);
assert!(weak.upgrade().is_none());
}
}