use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::ClientError;
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::subscription_data::SubscriptionData;
use tracing::warn;
use crate::consumer::consumer_impl::default_mq_push_consumer_impl::DefaultMQPushConsumerImpl;
use crate::consumer::consumer_impl::default_mq_push_consumer_impl::PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL;
use crate::consumer::consumer_impl::pop_request::PopRequest;
use crate::consumer::pop_result::PopResult;
use crate::consumer::pop_status::PopStatus;
#[trait_variant::make(PopCallback: Send)]
pub trait PopCallbackInner {
async fn on_success(&mut self, pop_result: PopResult);
fn on_error(&mut self, e: ClientError);
}
pub type PopCallbackFn = Arc<
dyn Fn(Option<PopResult>, Option<ClientError>) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> + Send + Sync,
>;
fn broker_response_code(error: &ClientError) -> Option<ResponseCode> {
error.broker_response_code().map(ResponseCode::from)
}
pub struct DefaultPopCallback {
pub(crate) push_consumer_impl: Arc<DefaultMQPushConsumerImpl>,
pub(crate) message_queue_inner: Option<MessageQueue>,
pub(crate) subscription_data: Option<SubscriptionData>,
pub(crate) pop_request: Option<PopRequest>,
}
impl PopCallback for DefaultPopCallback {
async fn on_success(&mut self, pop_result: PopResult) {
let push_consumer_impl = self.push_consumer_impl.clone();
let Some(message_queue_inner) = self.message_queue_inner.take() else {
warn!("pop callback success ignored: message queue is missing");
return;
};
let Some(subscription_data) = self.subscription_data.take() else {
warn!(
"pop callback success ignored: subscription data is missing, mq={}",
message_queue_inner
);
if let Some(pop_request) = self.pop_request.take() {
let delay = push_consumer_impl.pull_time_delay_mills_when_exception;
push_consumer_impl.execute_pop_request_later(pop_request, delay);
}
return;
};
let Some(pop_request) = self.pop_request.take() else {
warn!(
"pop callback success ignored: pop request is missing, mq={}",
message_queue_inner
);
return;
};
let pop_result = push_consumer_impl
.process_pop_result(pop_result, &subscription_data)
.await;
match pop_result.pop_status {
PopStatus::Found => {
if pop_result.msg_found_list.as_ref().is_none_or(|value| value.is_empty()) {
push_consumer_impl.execute_pop_request_immediately(pop_request).await;
} else {
let Some(consume_message_pop_service) = push_consumer_impl.consume_message_pop_service() else {
warn!(
"pop callback found messages but ConsumeMessagePopService is not initialized, mq={}",
message_queue_inner
);
let delay = push_consumer_impl.pull_time_delay_mills_when_exception;
push_consumer_impl.execute_pop_request_later(pop_request, delay);
return;
};
consume_message_pop_service
.submit_pop_consume_request(
pop_result.msg_found_list.unwrap_or_default(),
pop_request.get_pop_process_queue(),
pop_request.get_message_queue(),
)
.await;
let pull_interval = push_consumer_impl.consumer_config_snapshot().pull_interval;
if pull_interval > 0 {
push_consumer_impl.execute_pop_request_later(pop_request, pull_interval);
} else {
push_consumer_impl.execute_pop_request_immediately(pop_request).await;
}
}
}
PopStatus::NoNewMsg | PopStatus::PollingNotFound => {
push_consumer_impl.execute_pop_request_immediately(pop_request).await;
}
PopStatus::PollingFull => {
let pull_time_delay_mills_when_exception = push_consumer_impl.pull_time_delay_mills_when_exception;
push_consumer_impl.execute_pop_request_later(pop_request, pull_time_delay_mills_when_exception);
}
}
}
fn on_error(&mut self, err: ClientError) {
let push_consumer_impl = self.push_consumer_impl.clone();
let Some(message_queue_inner) = self.message_queue_inner.take() else {
warn!("pop callback exception ignored: message queue is missing, error={err}");
return;
};
let Some(pop_request) = self.pop_request.take() else {
warn!(
"pop callback exception ignored: pop request is missing, mq={}, error={}",
message_queue_inner, err
);
return;
};
let topic = message_queue_inner.topic_str();
let broker_code = broker_response_code(&err);
let consumer_group = push_consumer_impl.consumer_config_snapshot().consumer_group.clone();
if !topic.starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX) {
if broker_code == Some(ResponseCode::SubscriptionNotLatest) {
warn!("the subscription is not latest, group={}", consumer_group,);
} else {
warn!("execute the pop request exception, group={}", consumer_group);
}
}
let time_delay = if broker_code == Some(ResponseCode::FlowControl) {
PULL_TIME_DELAY_MILLS_WHEN_BROKER_FLOW_CONTROL
} else {
push_consumer_impl.pull_time_delay_mills_when_exception
};
push_consumer_impl.execute_pop_request_later(pop_request, time_delay);
}
}
#[cfg(test)]
mod tests {
use cheetah_string::CheetahString;
use rocketmq_model::common::message::message_ext::MessageExt;
use super::*;
use crate::base::client_config::ClientConfig;
use crate::consumer::consumer_impl::pop_process_queue::PopProcessQueue;
use crate::consumer::default_mq_push_consumer::ConsumerConfig;
fn message_queue() -> MessageQueue {
MessageQueue::from_parts("topic", "broker-a", 0)
}
fn pop_request() -> PopRequest {
PopRequest::new(
CheetahString::from_static_str("topic"),
CheetahString::from_static_str("group"),
message_queue(),
PopProcessQueue::new(),
0,
)
}
fn new_callback() -> DefaultPopCallback {
let consumer_config = ConsumerConfig::default();
let push_consumer_impl = Arc::new(DefaultMQPushConsumerImpl::new(
crate::runtime::test_client_runtime("pop-callback-test"),
ClientConfig::default(),
consumer_config,
None,
));
push_consumer_impl.initialize_self_reference();
DefaultPopCallback {
push_consumer_impl,
message_queue_inner: None,
subscription_data: None,
pop_request: None,
}
}
#[test]
fn broker_response_code_reads_broker_error_without_downcast() {
let error = crate::ClientError::broker_operation_failed(
"POP_MESSAGE",
ResponseCode::SubscriptionNotLatest.to_i32(),
"subscription not latest",
);
assert_eq!(broker_response_code(&error), Some(ResponseCode::SubscriptionNotLatest));
}
#[tokio::test]
async fn success_without_message_queue_is_ignored_without_panic() {
let mut callback = new_callback();
PopCallback::on_success(&mut callback, PopResult::default()).await;
}
#[tokio::test]
async fn success_without_subscription_data_is_delayed_without_panic() {
let mut callback = new_callback();
callback.message_queue_inner = Some(message_queue());
callback.pop_request = Some(pop_request());
PopCallback::on_success(&mut callback, PopResult::default()).await;
}
#[tokio::test]
async fn success_without_pop_request_is_ignored_without_panic() {
let mut callback = new_callback();
callback.message_queue_inner = Some(message_queue());
callback.subscription_data = Some(SubscriptionData::default());
PopCallback::on_success(&mut callback, PopResult::default()).await;
}
#[tokio::test]
async fn found_messages_without_pop_service_are_delayed_without_panic() {
let mut callback = new_callback();
callback.message_queue_inner = Some(message_queue());
callback.subscription_data = Some(SubscriptionData::default());
callback.pop_request = Some(pop_request());
let pop_result = PopResult {
msg_found_list: Some(vec![MessageExt::default()]),
pop_status: PopStatus::Found,
..Default::default()
};
PopCallback::on_success(&mut callback, pop_result).await;
}
#[test]
fn error_without_message_queue_is_ignored_without_panic() {
let mut callback = new_callback();
PopCallback::on_error(&mut callback, ClientError::illegal_argument("test error"));
}
#[test]
fn error_without_pop_request_is_ignored_without_panic() {
let mut callback = new_callback();
callback.message_queue_inner = Some(message_queue());
PopCallback::on_error(&mut callback, ClientError::illegal_argument("test error"));
}
}