This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/rocketmq-clients.git


The following commit(s) were added to refs/heads/master by this push:
     new 3a47e1de [ISSUE #1406] [Rust] Take PushConsumer filter expression from 
the subscription (#1407)
3a47e1de is described below

commit 3a47e1de95deea3909b6ff64fd26cc0b24d0e611
Author: Lei Zhiyuan <[email protected]>
AuthorDate: Fri Oct 23 09:00:38 2026 +0800

    [ISSUE #1406] [Rust] Take PushConsumer filter expression from the 
subscription (#1407)
    
    The filter expression used by ReceiveMessage was looked up by the assigned
    message queue's topic name. Servers may return a decorated name such as
    "<instance>%<topic>", so the lookup failed, no ReceiveMessage was sent and
    the worker spun on a 1 ms ticker without backoff.
    
    Align with the Java client:
    - pass the subscription's FilterExpression from scanAssignments to
      MessageQueueActor (PushConsumerImpl#syncProcessQueue / ProcessQueueImpl)
    - back off 20 ms on TooManyRequests and 1 s on other receive failures
      (ProcessQueueImpl#onReceiveMessageException)
---
 rust/src/push_consumer.rs | 186 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 166 insertions(+), 20 deletions(-)

diff --git a/rust/src/push_consumer.rs b/rust/src/push_consumer.rs
index 31d9ea38..60ce721d 100644
--- a/rust/src/push_consumer.rs
+++ b/rust/src/push_consumer.rs
@@ -30,7 +30,7 @@ use tokio_util::task::TaskTracker;
 use crate::client::Client;
 use crate::conf::{BackOffRetryPolicy, ClientOption, PushConsumerOption};
 use crate::error::{ClientError, ErrorKind};
-use crate::model::common::{ClientType, ConsumeResult, MessageQueue};
+use crate::model::common::{ClientType, ConsumeResult, FilterExpression, 
MessageQueue};
 use crate::model::message::{AckMessageEntry, MessageView};
 use crate::pb;
 use crate::pb::receive_message_response::Content;
@@ -55,6 +55,24 @@ const OPERATION_START_PUSH_CONSUMER: &str = 
"push_consumer.start";
 const OPERATION_CHANGE_INVISIBLE_DURATION: &str = 
"push_consumer.change_invisible_duration";
 const OPERATION_FORWARD_TO_DEADLETTER_QUEUE: &str = 
"push_consumer.forward_to_deadletter_queue";
 
+// Reference Java: ProcessQueueImpl.RECEIVING_FLOW_CONTROL_BACKOFF_DELAY / 
RECEIVING_FAILURE_BACKOFF_DELAY
+const RECEIVING_FLOW_CONTROL_BACKOFF_DELAY: Duration = 
Duration::from_millis(20);
+const RECEIVING_FAILURE_BACKOFF_DELAY: Duration = Duration::from_secs(1);
+
+/// Delay before the next receive attempt after a failure.
+fn receive_failure_backoff_delay(error: &ClientError) -> Duration {
+    let too_many_requests = (pb::Code::TooManyRequests as i32).to_string();
+    let flow_controlled = error
+        .context()
+        .iter()
+        .any(|(key, value)| *key == "code" && *value == too_many_requests);
+    if flow_controlled {
+        RECEIVING_FLOW_CONTROL_BACKOFF_DELAY
+    } else {
+        RECEIVING_FAILURE_BACKOFF_DELAY
+    }
+}
+
 pub type MessageListener = Box<dyn Fn(&MessageView) -> ConsumeResult + Send + 
Sync>;
 
 pub struct PushConsumer {
@@ -185,7 +203,7 @@ impl PushConsumer {
                         }
                         let subscription_table = 
consumer_option.subscription_expressions();
                         // query endpoints from topic route
-                        for topic in subscription_table.keys() {
+                        for (topic, filter_expression) in 
subscription_table.iter() {
                             if let Some(endpoints) = 
route_manager.pick_endpoints(topic.as_str()) {
                                 let request = QueryAssignmentRequest {
                                     topic: Some(Resource {
@@ -204,6 +222,7 @@ impl PushConsumer {
                                             let result = 
Self::process_assignments(
                                                 &rpc_client,
                                                 &consumer_option,
+                                                filter_expression,
                                                 Arc::clone(&message_listener),
                                                 &mut actor_table,
                                                 response.assignments,
@@ -297,7 +316,7 @@ impl PushConsumer {
                         }
                         let subscription_table = 
consumer_option.subscription_expressions();
                         // query endpoints from topic route
-                        for topic in subscription_table.keys() {
+                        for (topic, filter_expression) in 
subscription_table.iter() {
                             if let Some(endpoints) = 
route_manager.pick_endpoints(topic.as_str()) {
                                 let request = QueryAssignmentRequest {
                                     topic: Some(Resource {
@@ -316,6 +335,7 @@ impl PushConsumer {
                                             let result = 
Self::process_assignments(
                                                 &rpc_client,
                                                 &consumer_option,
+                                                filter_expression,
                                                 Arc::clone(&message_listener),
                                                 &mut actor_table,
                                                 response.assignments,
@@ -352,6 +372,7 @@ impl PushConsumer {
     async fn process_assignments(
         rpc_client: &Session,
         option: &PushConsumerOption,
+        filter_expression: &FilterExpression,
         message_listener: Arc<MessageListener>,
         actor_table: &mut HashMap<MessageQueue, MessageQueueActor>,
         mut assignments: Vec<Assignment>,
@@ -396,6 +417,7 @@ impl PushConsumer {
             let mut actor = MessageQueueActor::new(
                 rpc_client.shadow_session(),
                 message_queue.clone(),
+                filter_expression.clone(),
                 option,
                 Arc::clone(&message_listener),
                 retry_policy.clone(),
@@ -436,15 +458,9 @@ impl PushConsumer {
     async fn receive_messages<T: RPCClient + 'static>(
         rpc_client: &mut T,
         message_queue: &MessageQueue,
+        filter_expression: &FilterExpression,
         option: &PushConsumerOption,
     ) -> Result<Vec<MessageView>, ClientError> {
-        let filter_expression = option
-            .get_filter_expression(&message_queue.topic.name)
-            .ok_or(ClientError::new(
-                ErrorKind::Unknown,
-                "no filter expression presents",
-                OPERATION_RECEIVE_MESSAGE,
-            ))?;
         let request = ReceiveMessageRequest {
             group: Some(option.get_consumer_group_resource()),
             message_queue: Some(message_queue.to_pb_message_queue()),
@@ -580,6 +596,10 @@ impl DlqEntry {
 struct MessageQueueActor {
     rpc_client: Session,
     message_queue: MessageQueue,
+    // Taken from the subscription table when the queue is assigned (same as 
Java's
+    // ProcessQueueImpl). Never look it up by the assigned queue's topic name, 
because
+    // servers may return a decorated name such as "<instance>%<topic>".
+    filter_expression: FilterExpression,
     option: PushConsumerOption,
     message_listener: Arc<MessageListener>,
     retry_policy: BackOffRetryPolicy,
@@ -592,6 +612,7 @@ impl MessageQueueActor {
     pub(crate) fn new(
         rpc_client: Session,
         message_queue: MessageQueue,
+        filter_expression: FilterExpression,
         option: PushConsumerOption,
         message_listener: Arc<MessageListener>,
         retry_policy: BackOffRetryPolicy,
@@ -599,6 +620,7 @@ impl MessageQueueActor {
         Self {
             rpc_client,
             message_queue,
+            filter_expression,
             option,
             message_listener: Arc::clone(&message_listener),
             retry_policy,
@@ -645,6 +667,7 @@ impl MessageQueueActor {
             }
             let mut ticker = tokio::time::interval(Duration::from_millis(1));
             let message_queue = self.message_queue.clone();
+            let filter_expression = self.filter_expression.clone();
             let option = self.option.clone();
             let retry_policy = self.retry_policy.clone();
             // TODO: Pass is_lite_consumer from Client when LitePushConsumer 
is fully integrated
@@ -660,9 +683,15 @@ impl MessageQueueActor {
                 loop {
                     select! {
                         _ = ticker.tick() => {
-                            let result = 
consumer_worker.receive_messages(&message_queue, &option, &mut ack_processor, 
&retry_policy).await;
-                            if result.is_err() {
-                                error!("receive messages error: {:?}", 
result.err());
+                            let result = 
consumer_worker.receive_messages(&message_queue, &filter_expression, &option, 
&mut ack_processor, &retry_policy).await;
+                            if let Err(e) = result {
+                                // Reference Java: 
ProcessQueueImpl.onReceiveMessageException()
+                                let delay = receive_failure_backoff_delay(&e);
+                                error!("receive messages error, retry after 
{:?}, mq={:?}: {:?}", delay, message_queue, e);
+                                select! {
+                                    _ = tokio::time::sleep(delay) => {}
+                                    _ = shutdown_token.cancelled() => {}
+                                }
                             }
                         }
                         _ = shutdown_token.cancelled() => {
@@ -698,6 +727,7 @@ impl ConsumerWorker {
     async fn receive_messages(
         &mut self,
         message_queue: &MessageQueue,
+        filter_expression: &FilterExpression,
         option: &PushConsumerOption,
         ack_processor: &mut AckEntryProcessor,
         retry_policy: &BackOffRetryPolicy,
@@ -705,12 +735,24 @@ impl ConsumerWorker {
         match self {
             ConsumerWorker::Standard(consumer_worker) => {
                 consumer_worker
-                    .receive_messages(message_queue, option, ack_processor, 
retry_policy)
+                    .receive_messages(
+                        message_queue,
+                        filter_expression,
+                        option,
+                        ack_processor,
+                        retry_policy,
+                    )
                     .await
             }
             ConsumerWorker::Fifo(consumer_worker) => {
                 consumer_worker
-                    .receive_messages(message_queue, option, ack_processor, 
retry_policy)
+                    .receive_messages(
+                        message_queue,
+                        filter_expression,
+                        option,
+                        ack_processor,
+                        retry_policy,
+                    )
                     .await
             }
         }
@@ -733,12 +775,18 @@ impl StandardConsumerWorker {
     async fn receive_messages(
         &mut self,
         message_queue: &MessageQueue,
+        filter_expression: &FilterExpression,
         option: &PushConsumerOption,
         ack_processor: &mut AckEntryProcessor,
         retry_policy: &BackOffRetryPolicy,
     ) -> Result<(), ClientError> {
-        let messages =
-            PushConsumer::receive_messages(&mut self.rpc_client, 
message_queue, option).await?;
+        let messages = PushConsumer::receive_messages(
+            &mut self.rpc_client,
+            message_queue,
+            filter_expression,
+            option,
+        )
+        .await?;
         for message in messages {
             let consume_result = (self.message_listener)(&message);
             match consume_result {
@@ -774,12 +822,18 @@ impl FifoConsumerWorker {
     async fn receive_messages(
         &mut self,
         message_queue: &MessageQueue,
+        filter_expression: &FilterExpression,
         option: &PushConsumerOption,
         ack_processor: &mut AckEntryProcessor,
         retry_policy: &BackOffRetryPolicy,
     ) -> Result<(), ClientError> {
-        let messages =
-            PushConsumer::receive_messages(&mut self.rpc_client, 
message_queue, option).await?;
+        let messages = PushConsumer::receive_messages(
+            &mut self.rpc_client,
+            message_queue,
+            filter_expression,
+            option,
+        )
+        .await?;
 
         // If FIFO consume accelerator is enabled, consume messages in 
parallel by messageGroup
         if option.enable_fifo_consume_accelerator() && !messages.is_empty() {
@@ -1307,7 +1361,7 @@ mod tests {
             }),
         }];
         let context = MockMessageQueueActor::new_context();
-        context.expect().returning(|_, _, _, _, _| {
+        context.expect().returning(|_, _, _, _, _, _| {
             let mut actor = MockMessageQueueActor::default();
             actor.expect_start().returning(|| Ok(()));
             return actor;
@@ -1315,6 +1369,7 @@ mod tests {
         PushConsumer::process_assignments(
             &rpc_client,
             option,
+            &FilterExpression::sub_all(),
             message_listener,
             &mut actor_table,
             assignments,
@@ -1370,6 +1425,7 @@ mod tests {
         PushConsumer::process_assignments(
             &rpc_client,
             option,
+            &FilterExpression::sub_all(),
             message_listener,
             &mut actor_table,
             assignments,
@@ -1447,6 +1503,7 @@ mod tests {
         PushConsumer::process_assignments(
             &session,
             option,
+            &FilterExpression::sub_all(),
             message_listener,
             &mut actor_table,
             assignments.clone(),
@@ -1478,6 +1535,7 @@ mod tests {
         PushConsumer::process_assignments(
             &session,
             option,
+            &FilterExpression::sub_all(),
             Arc::new(Box::new(|_| ConsumeResult::SUCCESS)),
             &mut actor_table,
             assignments2,
@@ -1568,6 +1626,7 @@ mod tests {
         let mut actor = MessageQueueActor::new(
             session,
             message_queue,
+            FilterExpression::sub_all(),
             option,
             Arc::new(Box::new(|_| ConsumeResult::SUCCESS)),
             retry_policy,
@@ -1601,6 +1660,7 @@ mod tests {
         let mut actor = MessageQueueActor::new(
             session,
             message_queue,
+            FilterExpression::sub_all(),
             option,
             Arc::new(Box::new(|_| ConsumeResult::SUCCESS)),
             retry_policy,
@@ -1953,12 +2013,95 @@ mod tests {
         let result = ConsumerWorker::Standard(consumer_worker)
             .receive_messages(
                 &new_message_queue(),
+                &FilterExpression::sub_all(),
+                &option,
+                &mut ack_processor,
+                retry_policy,
+            )
+            .await;
+        assert!(result.is_ok(), "{:?}", result);
+    }
+
+    #[test]
+    fn test_receive_failure_backoff_delay() {
+        let flow_control = ClientError::new(ErrorKind::Server, "too many 
requests", "test")
+            .with_context("code", format!("{}", Code::TooManyRequests as i32));
+        assert_eq!(
+            Duration::from_millis(20),
+            receive_failure_backoff_delay(&flow_control)
+        );
+
+        let other_code = ClientError::new(ErrorKind::Server, "internal error", 
"test")
+            .with_context("code", format!("{}", Code::InternalError as i32));
+        assert_eq!(
+            Duration::from_secs(1),
+            receive_failure_backoff_delay(&other_code)
+        );
+
+        let transport = ClientError::new(ErrorKind::ClientInternal, "rpc 
failed", "test");
+        assert_eq!(
+            Duration::from_secs(1),
+            receive_failure_backoff_delay(&transport)
+        );
+    }
+
+    // Some servers (e.g. TDMQ) return assignment topics with an instance 
prefix such as
+    // "rmq-xxx%test_topic", which differs from the subscription key 
"test_topic".
+    // The filter expression must come from the subscription, not be looked up 
by the
+    // topic name of the assigned queue.
+    #[tokio::test]
+    async fn test_receive_messages_with_prefixed_topic_name() {
+        let mut rpc_client = Session::default();
+        rpc_client
+            .expect_receive_message()
+            .times(1)
+            .returning(|request| {
+                let topic = request.message_queue.unwrap().topic.unwrap();
+                assert_eq!("rmq-xxx%test_topic", topic.name);
+                let filter = request.filter_expression.unwrap();
+                assert_eq!("tagA", filter.expression);
+                assert_eq!(FilterType::Tag as i32, filter.r#type);
+                Ok(vec![ReceiveMessageResponse {
+                    content: Some(Content::Message(new_message())),
+                }])
+            });
+        let consumer_worker =
+            StandardConsumerWorker::new(rpc_client, Arc::new(Box::new(|_| 
ConsumeResult::SUCCESS)));
+        let mut message_queue = new_message_queue();
+        message_queue.topic.name = "rmq-xxx%test_topic".to_string();
+        let mut option = PushConsumerOption::default();
+        option.subscribe("test_topic", FilterExpression::new(FilterType::Tag, 
"tagA"));
+        let retry_policy =
+            
&BackOffRetryPolicy::Exponential(ExponentialBackOffRetryPolicy::default());
+        let mut session = Session::default();
+        session.expect_shadow_session().returning(Session::default);
+        session.expect_ack_message().times(1).returning(|_| {
+            Ok(AckMessageResponse {
+                status: Some(Status {
+                    code: Code::Ok as i32,
+                    message: "".to_string(),
+                }),
+                entries: vec![],
+            })
+        });
+        let mut ack_processor = AckEntryProcessor::new(
+            session,
+            option.get_consumer_group_resource(),
+            message_queue.topic.clone(),
+            false,
+        );
+        assert!(ack_processor.start().await.is_ok());
+        let result = ConsumerWorker::Standard(consumer_worker)
+            .receive_messages(
+                &message_queue,
+                option.get_filter_expression("test_topic").unwrap(),
                 &option,
                 &mut ack_processor,
                 retry_policy,
             )
             .await;
         assert!(result.is_ok(), "{:?}", result);
+        ack_processor.shutdown().await;
     }
 
     #[tokio::test]
@@ -2012,6 +2155,7 @@ mod tests {
         let result = ConsumerWorker::Standard(consumer_worker)
             .receive_messages(
                 &new_message_queue(),
+                &FilterExpression::sub_all(),
                 &option,
                 &mut ack_processor,
                 retry_policy,
@@ -2063,6 +2207,7 @@ mod tests {
         let result = ConsumerWorker::Fifo(consumer_worker)
             .receive_messages(
                 &new_message_queue(),
+                &FilterExpression::sub_all(),
                 &option,
                 &mut ack_processor,
                 retry_policy,
@@ -2124,6 +2269,7 @@ mod tests {
         let result = ConsumerWorker::Fifo(consumer_worker)
             .receive_messages(
                 &new_message_queue(),
+                &FilterExpression::sub_all(),
                 &option,
                 &mut ack_processor,
                 retry_policy,

Reply via email to