Skip to main content

rocketmq_client_rust/consumer/
default_lite_pull_consumer.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::collections::HashSet;
17use std::sync::Arc;
18use std::sync::RwLock as StdRwLock;
19
20use cheetah_string::CheetahString;
21use rocketmq_common::common::consumer::consume_from_where::ConsumeFromWhere;
22use rocketmq_common::common::message::message_decoder;
23use rocketmq_common::common::message::message_ext::MessageExt;
24use rocketmq_common::common::message::message_queue::MessageQueue;
25use rocketmq_common::common::topic::TopicValidator;
26use rocketmq_error::RocketMQError;
27use rocketmq_error::RocketMQResult;
28use rocketmq_remoting::protocol::body::consumer_running_info::ConsumerRunningInfo;
29use rocketmq_remoting::protocol::heartbeat::message_model::MessageModel;
30use rocketmq_remoting::protocol::heartbeat::subscription_data::SubscriptionData;
31use rocketmq_remoting::protocol::namespace_util::NamespaceUtil;
32use rocketmq_remoting::runtime::RPCHook;
33use rocketmq_rust::ArcMut;
34use tokio::sync::OnceCell;
35use tokio::sync::RwLock;
36
37use crate::base::client_config::ClientConfig;
38use crate::base::query_result::QueryResult;
39use crate::consumer::allocate_message_queue_strategy::AllocateMessageQueueStrategy;
40use crate::consumer::consumer_impl::default_lite_pull_consumer_impl::validate_lite_pull_consume_from_where;
41use crate::consumer::consumer_impl::default_lite_pull_consumer_impl::DefaultLitePullConsumerImpl;
42use crate::consumer::consumer_impl::default_lite_pull_consumer_impl::LitePullConsumerConfig;
43use crate::consumer::default_lite_pull_consumer_builder::DefaultLitePullConsumerBuilder;
44use crate::consumer::default_lite_pull_consumer_builder::MIN_AUTOCOMMIT_INTERVAL_MILLIS;
45use crate::consumer::lite_pull_consumer::LitePullConsumer;
46use crate::consumer::message_queue_listener::ArcMessageQueueListener;
47use crate::consumer::message_queue_listener::MessageQueueListener;
48use crate::consumer::message_selector::MessageSelector;
49use crate::consumer::mq_consumer::MQConsumer;
50use crate::consumer::mq_consumer_inner::MQConsumerInner;
51use crate::consumer::store::offset_store::OffsetStore;
52use crate::consumer::topic_message_queue_change_listener::TopicMessageQueueChangeListener;
53use crate::trace::async_trace_dispatcher::AsyncTraceDispatcher;
54use crate::trace::hook::consume_message_trace_hook_impl::ConsumeMessageTraceHookImpl;
55use crate::trace::trace_dispatcher::TraceDispatcher;
56use crate::trace::trace_dispatcher::Type;
57
58/// Default implementation of a lite pull consumer.
59///
60/// This is the main entry point for creating and using a lite pull consumer. It acts as a facade
61/// over the internal implementation ([`DefaultLitePullConsumerImpl`]) and provides:
62///
63/// - Configuration management via [`DefaultLitePullConsumerBuilder`]
64/// - Namespace handling (automatic topic name wrapping/unwrapping)
65/// - Optional message trace integration
66/// - A clean public API that delegates to the internal implementation
67///
68/// # Architecture
69///
70/// ```text
71/// ┌────────────────────────────────────┐
72/// │   DefaultLitePullConsumer          │  ← Public Facade
73/// │  (config, trace, namespace)        │
74/// └──────────────┬─────────────────────┘
75///                │ delegates
76///                ↓
77/// ┌────────────────────────────────────┐
78/// │ DefaultLitePullConsumerImpl        │  ← Core Logic
79/// │  (lifecycle, pull, commit, etc.)   │
80/// └────────────────────────────────────┘
81/// ```
82///
83/// # Examples
84///
85/// ## Basic usage with auto-commit
86///
87/// ```rust,ignore
88/// use rocketmq_client::consumer::default_lite_pull_consumer::DefaultLitePullConsumer;
89///
90/// let consumer = DefaultLitePullConsumer::builder()
91///     .consumer_group("my_consumer_group")
92///     .name_server_addr("127.0.0.1:9876")
93///     .auto_commit(true)
94///     .build()?;
95///
96/// consumer.start().await?;
97/// consumer.subscribe("my_topic").await?;
98///
99/// loop {
100///     let messages = consumer.poll_with_timeout(1000).await;
101///     for msg in messages {
102///         println!("Received: {:?}", msg);
103///     }
104///     // Offsets are auto-committed
105/// }
106/// ```
107///
108/// ## Manual offset control
109///
110/// ```rust,ignore
111/// let consumer = DefaultLitePullConsumer::builder()
112///     .consumer_group("my_consumer_group")
113///     .name_server_addr("127.0.0.1:9876")
114///     .auto_commit(false)  // Disable auto-commit
115///     .build()?;
116///
117/// consumer.start().await?;
118/// consumer.subscribe("my_topic").await?;
119///
120/// loop {
121///     let messages = consumer.poll_with_timeout(1000).await;
122///     for msg in messages {
123///         // Process message
124///     }
125///     
126///     // Manually commit after processing
127///     consumer.commit_all().await?;
128/// }
129/// ```
130///
131/// ## Manual queue assignment (no rebalance)
132///
133/// ```rust,ignore
134/// use rocketmq_common::common::message::message_queue::MessageQueue;
135///
136/// let mq1 = MessageQueue::from_parts("my_topic", "broker-a", 0);
137/// let mq2 = MessageQueue::from_parts("my_topic", "broker-a", 1);
138///
139/// consumer.start().await?;
140/// consumer.assign(vec![mq1, mq2]).await?;
141///
142/// let messages = consumer.poll().await;
143/// ```
144#[derive(Clone)]
145pub struct DefaultLitePullConsumer {
146    /// Client configuration (network, instance name, etc.)
147    client_config: ArcMut<ClientConfig>,
148
149    /// Consumer-specific configuration (pull batch size, flow control, etc.)
150    consumer_config: ArcMut<LitePullConsumerConfig>,
151
152    /// Core implementation (lazy-initialized on start using OnceCell)
153    default_lite_pull_consumer_impl: Arc<OnceCell<ArcMut<DefaultLitePullConsumerImpl>>>,
154
155    /// Optional RPC hook for request/response interception
156    rpc_hook: Option<Arc<dyn RPCHook>>,
157
158    /// Optional trace dispatcher for message tracing (with interior mutability)
159    trace_dispatcher: Arc<RwLock<Option<Arc<dyn TraceDispatcher + Send + Sync>>>>,
160
161    /// Whether message trace is enabled
162    enable_msg_trace: bool,
163
164    /// Custom trace topic (if specified)
165    custom_trace_topic: Option<CheetahString>,
166
167    /// User listener notified after LitePull rebalance updates assigned queues.
168    message_queue_listener: Arc<StdRwLock<Option<ArcMessageQueueListener>>>,
169
170    /// User-provided or initialized offset store.
171    offset_store: Arc<StdRwLock<Option<ArcMut<OffsetStore>>>>,
172}
173
174impl DefaultLitePullConsumer {
175    /// Creates a new consumer with the specified configuration.
176    ///
177    /// Most users should use [`builder()`](Self::builder) instead.
178    pub fn new(
179        client_config: ArcMut<ClientConfig>,
180        consumer_config: ArcMut<LitePullConsumerConfig>,
181        rpc_hook: Option<Arc<dyn RPCHook>>,
182        trace_dispatcher: Option<Arc<dyn TraceDispatcher + Send + Sync>>,
183        enable_msg_trace: bool,
184        custom_trace_topic: Option<CheetahString>,
185    ) -> Self {
186        Self {
187            client_config,
188            consumer_config,
189            default_lite_pull_consumer_impl: Arc::new(OnceCell::new()),
190            rpc_hook,
191            trace_dispatcher: Arc::new(RwLock::new(trace_dispatcher)),
192            enable_msg_trace,
193            custom_trace_topic,
194            message_queue_listener: Arc::new(StdRwLock::new(None)),
195            offset_store: Arc::new(StdRwLock::new(None)),
196        }
197    }
198
199    /// Returns a builder for creating a consumer with custom configuration.
200    ///
201    /// # Examples
202    ///
203    /// ```rust,ignore
204    /// let consumer = DefaultLitePullConsumer::builder()
205    ///     .consumer_group("my_group")
206    ///     .name_server_addr("127.0.0.1:9876")
207    ///     .pull_batch_size(32)
208    ///     .build()?;
209    /// ```
210    pub fn builder() -> DefaultLitePullConsumerBuilder {
211        DefaultLitePullConsumerBuilder::new()
212    }
213
214    /// Starts the consumer.
215    pub async fn start(&self) -> RocketMQResult<()> {
216        <Self as LitePullConsumer>::start(self).await
217    }
218
219    /// Shuts down the consumer.
220    pub async fn shutdown(&self) {
221        <Self as LitePullConsumer>::shutdown(self).await;
222    }
223
224    /// Returns whether the consumer is currently running.
225    pub async fn is_running(&self) -> bool {
226        <Self as LitePullConsumer>::is_running(self).await
227    }
228
229    /// Returns a Java-compatible running snapshot for broker admin diagnostics.
230    pub async fn consumer_running_info(&self) -> RocketMQResult<ConsumerRunningInfo> {
231        let impl_ = self.try_impl_()?;
232        Ok(MQConsumerInner::consumer_running_info(impl_.as_ref()).await)
233    }
234
235    /// Subscribes to a topic using the default subscription expression.
236    pub async fn subscribe(&self, topic: &str) -> RocketMQResult<()> {
237        <Self as LitePullConsumer>::subscribe(self, topic).await
238    }
239
240    /// Subscribes to a topic using a tag or SQL expression.
241    pub async fn subscribe_with_expression(&self, topic: &str, sub_expression: &str) -> RocketMQResult<()> {
242        <Self as LitePullConsumer>::subscribe_with_expression(self, topic, sub_expression).await
243    }
244
245    /// Subscribes with a queue-change listener.
246    pub async fn subscribe_with_listener<MQL>(
247        &self,
248        topic: &str,
249        sub_expression: &str,
250        listener: MQL,
251    ) -> RocketMQResult<()>
252    where
253        MQL: MessageQueueListener + 'static,
254    {
255        <Self as LitePullConsumer>::subscribe_with_listener(self, topic, sub_expression, listener).await
256    }
257
258    /// Subscribes using a server-side selector.
259    pub async fn subscribe_with_selector(&self, topic: &str, selector: Option<MessageSelector>) -> RocketMQResult<()> {
260        <Self as LitePullConsumer>::subscribe_with_selector(self, topic, selector).await
261    }
262
263    /// Removes the subscription for a topic.
264    pub async fn unsubscribe(&self, topic: &str) {
265        <Self as LitePullConsumer>::unsubscribe(self, topic).await;
266    }
267
268    /// Returns the currently assigned queues.
269    pub async fn assignment(&self) -> RocketMQResult<HashSet<MessageQueue>> {
270        <Self as LitePullConsumer>::assignment(self).await
271    }
272
273    /// Assigns queues manually, bypassing broker rebalance.
274    pub async fn assign(&self, message_queues: Vec<MessageQueue>) -> RocketMQResult<()> {
275        <Self as LitePullConsumer>::assign(self, message_queues).await
276    }
277
278    /// Sets the subscription expression used for manually assigned queues.
279    pub async fn set_sub_expression_for_assign(&self, topic: &str, sub_expression: &str) -> RocketMQResult<()> {
280        <Self as LitePullConsumer>::set_sub_expression_for_assign(self, topic, sub_expression).await
281    }
282
283    /// Builds subscription metadata for heartbeat payloads.
284    pub async fn build_subscriptions_for_heartbeat(
285        &self,
286        sub_expression_map: &mut HashMap<String, MessageSelector>,
287    ) -> RocketMQResult<()> {
288        <Self as LitePullConsumer>::build_subscriptions_for_heartbeat(self, sub_expression_map).await
289    }
290
291    /// Returns heartbeat subscriptions.
292    pub async fn subscriptions_for_heartbeat(&self) -> HashSet<SubscriptionData> {
293        <Self as LitePullConsumer>::subscriptions_for_heartbeat(self).await
294    }
295
296    /// Java-compatible getter alias for heartbeat subscriptions.
297    pub async fn get_subscriptions_for_heartbeat(&self) -> HashSet<SubscriptionData> {
298        self.subscriptions_for_heartbeat().await
299    }
300
301    /// Polls messages without cloning message payloads.
302    pub async fn poll_zero_copy(&self) -> Vec<ArcMut<MessageExt>> {
303        <Self as LitePullConsumer>::poll_zero_copy(self).await
304    }
305
306    /// Polls messages without cloning message payloads with a custom timeout.
307    pub async fn poll_with_timeout_zero_copy(&self, timeout: u64) -> Vec<ArcMut<MessageExt>> {
308        <Self as LitePullConsumer>::poll_with_timeout_zero_copy(self, timeout).await
309    }
310
311    /// Polls messages and returns owned copies.
312    pub async fn poll(&self) -> Vec<MessageExt> {
313        <Self as LitePullConsumer>::poll(self).await
314    }
315
316    /// Polls messages with a custom timeout and returns owned copies.
317    pub async fn poll_with_timeout(&self, timeout: u64) -> Vec<MessageExt> {
318        <Self as LitePullConsumer>::poll_with_timeout(self, timeout).await
319    }
320
321    /// Returns the configured message model.
322    pub async fn message_model(&self) -> MessageModel {
323        <Self as LitePullConsumer>::message_model(self).await
324    }
325
326    /// Java-compatible getter alias for the configured message model.
327    pub async fn get_message_model(&self) -> MessageModel {
328        self.message_model().await
329    }
330
331    /// Sets the configured message model.
332    pub async fn set_message_model(&self, message_model: MessageModel) {
333        <Self as LitePullConsumer>::set_message_model(self, message_model).await;
334    }
335
336    /// Returns where consumption starts when no offset exists.
337    pub async fn consume_from_where(&self) -> ConsumeFromWhere {
338        <Self as LitePullConsumer>::consume_from_where(self).await
339    }
340
341    /// Java-compatible getter alias for where consumption starts when no offset exists.
342    pub async fn get_consume_from_where(&self) -> ConsumeFromWhere {
343        self.consume_from_where().await
344    }
345
346    /// Sets where consumption starts when no offset exists.
347    pub async fn set_consume_from_where(&self, consume_from_where: ConsumeFromWhere) -> RocketMQResult<()> {
348        <Self as LitePullConsumer>::set_consume_from_where(self, consume_from_where).await
349    }
350
351    /// Returns the consume timestamp.
352    pub async fn consume_timestamp(&self) -> Option<CheetahString> {
353        <Self as LitePullConsumer>::consume_timestamp(self).await
354    }
355
356    /// Java-compatible getter alias for the consume timestamp.
357    pub async fn get_consume_timestamp(&self) -> Option<CheetahString> {
358        self.consume_timestamp().await
359    }
360
361    /// Sets the consume timestamp.
362    pub async fn set_consume_timestamp(&self, consume_timestamp: Option<CheetahString>) {
363        <Self as LitePullConsumer>::set_consume_timestamp(self, consume_timestamp).await;
364    }
365
366    /// Returns the queue allocation strategy.
367    pub async fn allocate_message_queue_strategy(&self) -> Arc<dyn AllocateMessageQueueStrategy + Send + Sync> {
368        <Self as LitePullConsumer>::allocate_message_queue_strategy(self).await
369    }
370
371    /// Java-compatible getter alias for the queue allocation strategy.
372    pub async fn get_allocate_message_queue_strategy(&self) -> Arc<dyn AllocateMessageQueueStrategy + Send + Sync> {
373        self.allocate_message_queue_strategy().await
374    }
375
376    /// Sets the queue allocation strategy.
377    pub async fn set_allocate_message_queue_strategy(
378        &self,
379        allocate_message_queue_strategy: Arc<dyn AllocateMessageQueueStrategy + Send + Sync>,
380    ) {
381        <Self as LitePullConsumer>::set_allocate_message_queue_strategy(self, allocate_message_queue_strategy).await;
382    }
383
384    /// Returns the message queue listener.
385    pub async fn message_queue_listener(&self) -> Option<ArcMessageQueueListener> {
386        <Self as LitePullConsumer>::message_queue_listener(self).await
387    }
388
389    /// Java-compatible getter alias for the message queue listener.
390    pub async fn get_message_queue_listener(&self) -> Option<ArcMessageQueueListener> {
391        self.message_queue_listener().await
392    }
393
394    /// Sets the message queue listener.
395    pub async fn set_message_queue_listener(&self, message_queue_listener: Option<ArcMessageQueueListener>) {
396        <Self as LitePullConsumer>::set_message_queue_listener(self, message_queue_listener).await;
397    }
398
399    /// Returns the configured offset store.
400    pub async fn offset_store(&self) -> Option<ArcMut<OffsetStore>> {
401        <Self as LitePullConsumer>::offset_store(self).await
402    }
403
404    /// Java-compatible getter alias for the configured offset store.
405    pub async fn get_offset_store(&self) -> Option<ArcMut<OffsetStore>> {
406        self.offset_store().await
407    }
408
409    /// Sets the configured offset store.
410    pub async fn set_offset_store(&self, offset_store: Option<ArcMut<OffsetStore>>) -> RocketMQResult<()> {
411        <Self as LitePullConsumer>::set_offset_store(self, offset_store).await
412    }
413
414    /// Returns the topic metadata check interval in milliseconds.
415    pub async fn topic_metadata_check_interval_millis(&self) -> u64 {
416        <Self as LitePullConsumer>::topic_metadata_check_interval_millis(self).await
417    }
418
419    /// Java-compatible getter alias for the topic metadata check interval.
420    pub async fn get_topic_metadata_check_interval_millis(&self) -> u64 {
421        self.topic_metadata_check_interval_millis().await
422    }
423
424    /// Sets the topic metadata check interval in milliseconds.
425    pub async fn set_topic_metadata_check_interval_millis(&self, interval_millis: u64) {
426        <Self as LitePullConsumer>::set_topic_metadata_check_interval_millis(self, interval_millis).await;
427    }
428
429    /// Returns the default poll timeout in milliseconds.
430    pub async fn poll_timeout_millis(&self) -> u64 {
431        <Self as LitePullConsumer>::poll_timeout_millis(self).await
432    }
433
434    /// Java-compatible getter alias for the default poll timeout.
435    pub async fn get_poll_timeout_millis(&self) -> u64 {
436        self.poll_timeout_millis().await
437    }
438
439    /// Sets the default poll timeout in milliseconds.
440    pub async fn set_poll_timeout_millis(&self, timeout_millis: u64) {
441        <Self as LitePullConsumer>::set_poll_timeout_millis(self, timeout_millis).await;
442    }
443
444    /// Returns the broker suspend max time in milliseconds.
445    pub async fn broker_suspend_max_time_millis(&self) -> u64 {
446        <Self as LitePullConsumer>::broker_suspend_max_time_millis(self).await
447    }
448
449    /// Java-compatible getter alias for the broker suspend max time.
450    pub async fn get_broker_suspend_max_time_millis(&self) -> u64 {
451        self.broker_suspend_max_time_millis().await
452    }
453
454    /// Sets the broker suspend max time in milliseconds.
455    pub async fn set_broker_suspend_max_time_millis(&self, timeout_millis: u64) {
456        <Self as LitePullConsumer>::set_broker_suspend_max_time_millis(self, timeout_millis).await;
457    }
458
459    /// Returns the consumer timeout while suspended.
460    pub async fn consumer_timeout_millis_when_suspend(&self) -> u64 {
461        <Self as LitePullConsumer>::consumer_timeout_millis_when_suspend(self).await
462    }
463
464    /// Java-compatible getter alias for the consumer timeout while suspended.
465    pub async fn get_consumer_timeout_millis_when_suspend(&self) -> u64 {
466        self.consumer_timeout_millis_when_suspend().await
467    }
468
469    /// Sets the consumer timeout while suspended.
470    pub async fn set_consumer_timeout_millis_when_suspend(&self, timeout_millis: u64) {
471        <Self as LitePullConsumer>::set_consumer_timeout_millis_when_suspend(self, timeout_millis).await;
472    }
473
474    /// Returns the pull RPC timeout in milliseconds.
475    pub async fn consumer_pull_timeout_millis(&self) -> u64 {
476        <Self as LitePullConsumer>::consumer_pull_timeout_millis(self).await
477    }
478
479    /// Java-compatible getter alias for the pull RPC timeout.
480    pub async fn get_consumer_pull_timeout_millis(&self) -> u64 {
481        self.consumer_pull_timeout_millis().await
482    }
483
484    /// Sets the pull RPC timeout in milliseconds.
485    pub async fn set_consumer_pull_timeout_millis(&self, timeout_millis: u64) {
486        <Self as LitePullConsumer>::set_consumer_pull_timeout_millis(self, timeout_millis).await;
487    }
488
489    /// Returns the pull batch size.
490    pub async fn pull_batch_size(&self) -> i32 {
491        <Self as LitePullConsumer>::pull_batch_size(self).await
492    }
493
494    /// Java-compatible getter alias for the pull batch size.
495    pub async fn get_pull_batch_size(&self) -> i32 {
496        self.pull_batch_size().await
497    }
498
499    /// Sets the pull batch size.
500    pub async fn set_pull_batch_size(&self, pull_batch_size: i32) {
501        <Self as LitePullConsumer>::set_pull_batch_size(self, pull_batch_size).await;
502    }
503
504    /// Returns the configured pull thread count.
505    pub async fn pull_thread_nums(&self) -> usize {
506        <Self as LitePullConsumer>::pull_thread_nums(self).await
507    }
508
509    /// Java-compatible getter alias for the configured pull thread count.
510    pub async fn get_pull_thread_nums(&self) -> usize {
511        self.pull_thread_nums().await
512    }
513
514    /// Sets the configured pull thread count.
515    pub async fn set_pull_thread_nums(&self, pull_thread_nums: usize) {
516        <Self as LitePullConsumer>::set_pull_thread_nums(self, pull_thread_nums).await;
517    }
518
519    /// Returns the all-queue pull threshold.
520    pub async fn pull_threshold_for_all(&self) -> i64 {
521        <Self as LitePullConsumer>::pull_threshold_for_all(self).await
522    }
523
524    /// Java-compatible getter alias for the all-queue pull threshold.
525    pub async fn get_pull_threshold_for_all(&self) -> i64 {
526        self.pull_threshold_for_all().await
527    }
528
529    /// Sets the all-queue pull threshold.
530    pub async fn set_pull_threshold_for_all(&self, pull_threshold_for_all: i64) {
531        <Self as LitePullConsumer>::set_pull_threshold_for_all(self, pull_threshold_for_all).await;
532    }
533
534    /// Returns the per-queue pull threshold.
535    pub async fn pull_threshold_for_queue(&self) -> i64 {
536        <Self as LitePullConsumer>::pull_threshold_for_queue(self).await
537    }
538
539    /// Java-compatible getter alias for the per-queue pull threshold.
540    pub async fn get_pull_threshold_for_queue(&self) -> i64 {
541        self.pull_threshold_for_queue().await
542    }
543
544    /// Sets the per-queue pull threshold.
545    pub async fn set_pull_threshold_for_queue(&self, pull_threshold_for_queue: i64) {
546        <Self as LitePullConsumer>::set_pull_threshold_for_queue(self, pull_threshold_for_queue).await;
547    }
548
549    /// Returns the per-queue pull size threshold.
550    pub async fn pull_threshold_size_for_queue(&self) -> i32 {
551        <Self as LitePullConsumer>::pull_threshold_size_for_queue(self).await
552    }
553
554    /// Java-compatible getter alias for the per-queue pull size threshold.
555    pub async fn get_pull_threshold_size_for_queue(&self) -> i32 {
556        self.pull_threshold_size_for_queue().await
557    }
558
559    /// Sets the per-queue pull size threshold.
560    pub async fn set_pull_threshold_size_for_queue(&self, pull_threshold_size_for_queue: i32) {
561        <Self as LitePullConsumer>::set_pull_threshold_size_for_queue(self, pull_threshold_size_for_queue).await;
562    }
563
564    /// Returns the max consume span.
565    pub async fn consume_max_span(&self) -> i64 {
566        <Self as LitePullConsumer>::consume_max_span(self).await
567    }
568
569    /// Java-compatible getter alias for the max consume span.
570    pub async fn get_consume_max_span(&self) -> i64 {
571        self.consume_max_span().await
572    }
573
574    /// Sets the max consume span.
575    pub async fn set_consume_max_span(&self, consume_max_span: i64) {
576        <Self as LitePullConsumer>::set_consume_max_span(self, consume_max_span).await;
577    }
578
579    /// Returns whether pulls use the configured broker id.
580    pub async fn is_connect_broker_by_user(&self) -> bool {
581        <Self as LitePullConsumer>::is_connect_broker_by_user(self).await
582    }
583
584    /// Sets whether pulls use the configured broker id.
585    pub async fn set_connect_broker_by_user(&self, connect_broker_by_user: bool) {
586        <Self as LitePullConsumer>::set_connect_broker_by_user(self, connect_broker_by_user).await;
587    }
588
589    /// Returns the configured default broker id.
590    pub async fn default_broker_id(&self) -> u64 {
591        <Self as LitePullConsumer>::default_broker_id(self).await
592    }
593
594    /// Java-compatible getter alias for the configured default broker id.
595    pub async fn get_default_broker_id(&self) -> u64 {
596        self.default_broker_id().await
597    }
598
599    /// Sets the configured default broker id.
600    pub async fn set_default_broker_id(&self, broker_id: u64) {
601        <Self as LitePullConsumer>::set_default_broker_id(self, broker_id).await;
602    }
603
604    /// Fetches message queues for a topic.
605    pub async fn fetch_message_queues(&self, topic: &str) -> RocketMQResult<Vec<MessageQueue>> {
606        <Self as LitePullConsumer>::fetch_message_queues(self, topic).await
607    }
608
609    /// Returns the committed offset for a queue.
610    pub async fn committed(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
611        <Self as LitePullConsumer>::committed(self, message_queue).await
612    }
613
614    /// Commits all offsets to the configured offset store.
615    pub async fn commit_all(&self) -> RocketMQResult<()> {
616        <Self as LitePullConsumer>::commit_all(self).await
617    }
618
619    /// Commits explicit offsets.
620    pub async fn commit_with_map(&self, offset_map: HashMap<MessageQueue, i64>, persist: bool) {
621        <Self as LitePullConsumer>::commit_with_map(self, offset_map, persist).await;
622    }
623
624    /// Commits all offsets.
625    pub async fn commit(&self) {
626        <Self as LitePullConsumer>::commit(self).await;
627    }
628
629    /// Commits offsets for selected queues.
630    pub async fn commit_with_set(&self, message_queues: HashSet<MessageQueue>, persist: bool) {
631        <Self as LitePullConsumer>::commit_with_set(self, message_queues, persist).await;
632    }
633
634    /// Java-compatible alias for committing all offsets.
635    pub async fn commit_sync(&self) {
636        <Self as LitePullConsumer>::commit_sync(self).await;
637    }
638
639    /// Java-compatible alias for committing explicit offsets.
640    pub async fn commit_sync_with_map(&self, offset_map: HashMap<MessageQueue, i64>, persist: bool) {
641        <Self as LitePullConsumer>::commit_sync_with_map(self, offset_map, persist).await;
642    }
643
644    /// Returns whether auto commit is enabled.
645    pub async fn is_auto_commit(&self) -> bool {
646        <Self as LitePullConsumer>::is_auto_commit(self).await
647    }
648
649    /// Sets whether auto commit is enabled.
650    pub async fn set_auto_commit(&self, auto_commit: bool) {
651        <Self as LitePullConsumer>::set_auto_commit(self, auto_commit).await;
652    }
653
654    /// Returns whether unit mode is enabled.
655    pub async fn is_unit_mode(&self) -> bool {
656        <Self as LitePullConsumer>::is_unit_mode(self).await
657    }
658
659    /// Sets whether unit mode is enabled.
660    pub async fn set_unit_mode(&self, unit_mode: bool) {
661        <Self as LitePullConsumer>::set_unit_mode(self, unit_mode).await;
662    }
663
664    /// Returns the auto commit interval in milliseconds.
665    pub async fn auto_commit_interval_millis(&self) -> u64 {
666        <Self as LitePullConsumer>::auto_commit_interval_millis(self).await
667    }
668
669    /// Java-compatible getter alias for the auto commit interval.
670    pub async fn get_auto_commit_interval_millis(&self) -> u64 {
671        self.auto_commit_interval_millis().await
672    }
673
674    /// Sets the auto commit interval in milliseconds.
675    pub async fn set_auto_commit_interval_millis(&self, interval_millis: u64) {
676        <Self as LitePullConsumer>::set_auto_commit_interval_millis(self, interval_millis).await;
677    }
678
679    /// Queries the offset for a timestamp.
680    pub async fn offset_for_timestamp(&self, message_queue: &MessageQueue, timestamp: u64) -> RocketMQResult<i64> {
681        <Self as LitePullConsumer>::offset_for_timestamp(self, message_queue, timestamp).await
682    }
683
684    /// Queries the earliest store time for a queue.
685    pub async fn earliest_msg_store_time(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
686        <Self as LitePullConsumer>::earliest_msg_store_time(self, message_queue).await
687    }
688
689    /// Queries the broker max offset for a queue.
690    pub async fn max_offset(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
691        <Self as LitePullConsumer>::max_offset(self, message_queue).await
692    }
693
694    /// Queries the broker min offset for a queue.
695    pub async fn min_offset(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
696        <Self as LitePullConsumer>::min_offset(self, message_queue).await
697    }
698
699    /// Creates a topic through the Java-compatible MQAdmin facade.
700    pub async fn create_topic(
701        &self,
702        key: &str,
703        new_topic: &str,
704        queue_num: i32,
705        attributes: HashMap<String, String>,
706    ) -> RocketMQResult<()> {
707        self.create_topic_with_flag(key, new_topic, queue_num, 0, attributes)
708            .await
709    }
710
711    /// Creates a topic with an explicit topic system flag.
712    pub async fn create_topic_with_flag(
713        &self,
714        key: &str,
715        new_topic: &str,
716        queue_num: i32,
717        topic_sys_flag: i32,
718        attributes: HashMap<String, String>,
719    ) -> RocketMQResult<()> {
720        let new_topic = self.with_namespace(new_topic);
721        self.try_impl_()?
722            .create_topic(key, new_topic.as_str(), queue_num, topic_sys_flag, attributes)
723            .await
724    }
725
726    /// Queries messages by key in a time range.
727    pub async fn query_message(
728        &self,
729        topic: &str,
730        key: &str,
731        max_num: i32,
732        begin: u64,
733        end: u64,
734    ) -> RocketMQResult<QueryResult> {
735        let topic = self.with_namespace(topic);
736        self.try_impl_()?
737            .query_message(topic.as_str(), key, max_num, begin, end)
738            .await
739    }
740
741    /// Views a message by offset message id or Java-compatible unique message id.
742    pub async fn view_message(&self, topic: &str, msg_id: &str) -> RocketMQResult<MessageExt> {
743        let topic = self.with_namespace(topic);
744        let impl_ = self.try_impl_()?;
745        if message_decoder::decode_message_id(msg_id).is_ok() {
746            impl_.view_message(topic.as_str(), msg_id).await
747        } else {
748            impl_.query_message_by_uniq_key(topic.as_str(), msg_id).await
749        }
750    }
751
752    /// Pauses fetching from selected queues.
753    pub async fn pause(&self, message_queues: Vec<MessageQueue>) {
754        <Self as LitePullConsumer>::pause(self, message_queues).await;
755    }
756
757    /// Resumes fetching from selected queues.
758    pub async fn resume(&self, message_queues: Vec<MessageQueue>) {
759        <Self as LitePullConsumer>::resume(self, message_queues).await;
760    }
761
762    /// Returns whether a queue is paused.
763    pub async fn is_paused(&self, message_queue: &MessageQueue) -> bool {
764        <Self as LitePullConsumer>::is_paused(self, message_queue).await
765    }
766
767    /// Seeks a queue to an offset.
768    pub async fn seek(&self, message_queue: &MessageQueue, offset: i64) -> RocketMQResult<()> {
769        <Self as LitePullConsumer>::seek(self, message_queue, offset).await
770    }
771
772    /// Seeks a queue to the beginning.
773    pub async fn seek_to_begin(&self, message_queue: &MessageQueue) -> RocketMQResult<()> {
774        <Self as LitePullConsumer>::seek_to_begin(self, message_queue).await
775    }
776
777    /// Seeks a queue to the end.
778    pub async fn seek_to_end(&self, message_queue: &MessageQueue) -> RocketMQResult<()> {
779        <Self as LitePullConsumer>::seek_to_end(self, message_queue).await
780    }
781
782    /// Updates the name server address.
783    pub async fn update_name_server_address(&self, name_server_address: &str) {
784        <Self as LitePullConsumer>::update_name_server_address(self, name_server_address).await;
785    }
786
787    /// Registers a topic queue-change listener.
788    pub async fn register_topic_message_queue_change_listener<TL>(
789        &self,
790        topic: &str,
791        listener: TL,
792    ) -> RocketMQResult<()>
793    where
794        TL: TopicMessageQueueChangeListener + 'static,
795    {
796        <Self as LitePullConsumer>::register_topic_message_queue_change_listener(self, topic, listener).await
797    }
798
799    /// Returns the consumer group name.
800    pub fn consumer_group(&self) -> &CheetahString {
801        &self.consumer_config.consumer_group
802    }
803
804    /// Java-compatible getter alias for the consumer group name.
805    pub fn get_consumer_group(&self) -> &CheetahString {
806        self.consumer_group()
807    }
808
809    /// Sets the consumer group name before the consumer starts.
810    ///
811    /// Like Java `DefaultLitePullConsumer.setConsumerGroup`, this updates the facade
812    /// configuration. If the internal implementation has already been initialized,
813    /// the value is synchronized into the impl and rebalance configuration while it
814    /// is still in the create state.
815    pub fn set_consumer_group(&self, consumer_group: impl Into<CheetahString>) -> RocketMQResult<()> {
816        let consumer_group = consumer_group.into();
817        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
818            impl_.set_consumer_group(consumer_group)?;
819        } else {
820            self.consumer_config.mut_from_ref().consumer_group = consumer_group;
821        }
822        Ok(())
823    }
824
825    /// Returns the namespace (if configured).
826    pub fn namespace(&self) -> Option<CheetahString> {
827        self.client_config.mut_from_ref().get_namespace()
828    }
829
830    /// Returns the custom trace topic, if configured.
831    pub fn customized_trace_topic(&self) -> Option<CheetahString> {
832        self.custom_trace_topic.clone()
833    }
834
835    /// Java-compatible getter alias for the custom trace topic.
836    pub fn get_customized_trace_topic(&self) -> Option<CheetahString> {
837        self.customized_trace_topic()
838    }
839
840    /// Sets the custom trace topic used when message tracing is enabled.
841    pub fn set_customized_trace_topic(&mut self, customized_trace_topic: impl Into<CheetahString>) {
842        self.custom_trace_topic = Some(customized_trace_topic.into());
843    }
844
845    /// Clears the custom trace topic so the default system trace topic is used.
846    pub fn clear_customized_trace_topic(&mut self) {
847        self.custom_trace_topic = None;
848    }
849
850    /// Returns whether message trace is enabled.
851    pub fn is_enable_msg_trace(&self) -> bool {
852        self.enable_msg_trace
853    }
854
855    /// Enables or disables message trace for subsequent startup.
856    pub fn set_enable_msg_trace(&mut self, enable_msg_trace: bool) {
857        self.enable_msg_trace = enable_msg_trace;
858    }
859
860    /// Returns whether outbound remoting connections should use TLS.
861    pub fn is_use_tls(&self) -> bool {
862        self.client_config.is_use_tls()
863    }
864
865    /// Enables or disables TLS on the shared client configuration and trace dispatcher.
866    pub async fn set_use_tls(&self, use_tls: bool) {
867        self.client_config.mut_from_ref().set_use_tls(use_tls);
868        if let Some(dispatcher) = self.trace_dispatcher.read().await.as_ref().cloned() {
869            if let Some(async_dispatcher) = dispatcher.as_any().downcast_ref::<AsyncTraceDispatcher>() {
870                async_dispatcher.set_use_tls(use_tls);
871            }
872        }
873    }
874
875    /// Returns the trace dispatcher, if tracing has been configured or initialized.
876    pub async fn trace_dispatcher(&self) -> Option<Arc<dyn TraceDispatcher + Send + Sync>> {
877        self.trace_dispatcher.read().await.as_ref().cloned()
878    }
879
880    /// Java-compatible getter alias for the trace dispatcher.
881    pub async fn get_trace_dispatcher(&self) -> Option<Arc<dyn TraceDispatcher + Send + Sync>> {
882        self.trace_dispatcher().await
883    }
884
885    /// Returns the client configuration.
886    pub fn client_config(&self) -> &ArcMut<ClientConfig> {
887        &self.client_config
888    }
889
890    /// Returns the consumer configuration.
891    pub fn consumer_config(&self) -> &ArcMut<LitePullConsumerConfig> {
892        &self.consumer_config
893    }
894
895    /// Wraps a topic name with the namespace (if configured).
896    fn with_namespace(&self, resource: &str) -> CheetahString {
897        self.client_config
898            .mut_from_ref()
899            .with_namespace(CheetahString::from(resource))
900    }
901
902    /// Removes namespace from a topic name (if configured).
903    fn without_namespace(&self, resource: &str) -> CheetahString {
904        match self.client_config.mut_from_ref().get_namespace() {
905            Some(namespace) if !namespace.is_empty() => {
906                NamespaceUtil::without_namespace_with_namespace(resource, namespace.as_str()).into()
907            }
908            _ => NamespaceUtil::without_namespace(resource).into(),
909        }
910    }
911
912    /// Wraps the consumer group with namespace before startup, matching Java LitePull start().
913    fn consumer_group_with_namespace(&self) -> CheetahString {
914        self.client_config
915            .mut_from_ref()
916            .with_namespace(self.consumer_config.consumer_group.clone())
917    }
918
919    /// Wraps a message queue with namespace.
920    fn queue_with_namespace(&self, mq: &MessageQueue) -> MessageQueue {
921        self.client_config.mut_from_ref().queue_with_namespace(mq.clone())
922    }
923
924    /// Removes namespace from a message queue.
925    fn queue_without_namespace(&self, mq: &MessageQueue) -> MessageQueue {
926        match self.client_config.mut_from_ref().get_namespace() {
927            Some(namespace) if !namespace.is_empty() => {
928                let unwrapped_topic = NamespaceUtil::without_namespace_with_namespace(mq.topic(), namespace.as_str());
929                MessageQueue::from_parts(unwrapped_topic, mq.broker_name().clone(), mq.queue_id())
930            }
931            Some(_) | None => mq.clone(),
932        }
933    }
934
935    /// Initializes the trace dispatcher if message trace is enabled.
936    async fn init_trace_dispatcher_internal(&self, impl_: &ArcMut<DefaultLitePullConsumerImpl>) -> RocketMQResult<()> {
937        if !self.enable_msg_trace {
938            return Ok(());
939        }
940
941        let dispatcher_arc = {
942            let mut dispatcher_guard = self.trace_dispatcher.write().await;
943            if let Some(dispatcher) = dispatcher_guard.as_ref() {
944                dispatcher.clone()
945            } else {
946                let trace_topic = self
947                    .custom_trace_topic
948                    .as_ref()
949                    .map(|t| t.as_str())
950                    .unwrap_or(TopicValidator::RMQ_SYS_TRACE_TOPIC);
951
952                let dispatcher = AsyncTraceDispatcher::new(
953                    self.consumer_config.consumer_group.as_str(),
954                    Type::Consume,
955                    self.client_config.trace_msg_batch_num,
956                    trace_topic,
957                    self.rpc_hook.clone(),
958                );
959                dispatcher.set_namespace_v2(self.client_config.namespace_v2.clone());
960                dispatcher.set_use_tls(self.client_config.use_tls);
961
962                let dispatcher_arc: Arc<dyn TraceDispatcher + Send + Sync> = Arc::new(dispatcher);
963                *dispatcher_guard = Some(dispatcher_arc.clone());
964                dispatcher_arc
965            }
966        };
967
968        let hook = Arc::new(ConsumeMessageTraceHookImpl::new(dispatcher_arc));
969        impl_.mut_from_ref().register_consume_message_hook(hook);
970
971        Ok(())
972    }
973
974    /// Starts the configured trace dispatcher after the consumer implementation is running.
975    async fn start_trace_dispatcher(&self) {
976        if !self.enable_msg_trace {
977            return;
978        }
979
980        let dispatcher = self.trace_dispatcher.read().await.as_ref().cloned();
981        let Some(dispatcher) = dispatcher else {
982            return;
983        };
984
985        let name_server_addr = self
986            .client_config
987            .namesrv_addr
988            .as_ref()
989            .map(|addr| addr.as_str())
990            .unwrap_or_default();
991        if let Err(error) = dispatcher.start(name_server_addr, self.client_config.access_channel) {
992            tracing::warn!("trace dispatcher start failed: {}", error);
993        }
994    }
995
996    /// Returns a reference to the internal implementation after it has been initialized.
997    fn try_impl_(&self) -> RocketMQResult<&ArcMut<DefaultLitePullConsumerImpl>> {
998        self.default_lite_pull_consumer_impl
999            .get()
1000            .ok_or_else(|| RocketMQError::not_initialized("DefaultLitePullConsumer not started. Call start() first."))
1001    }
1002
1003    /// Returns the internal implementation, initializing it without starting network services.
1004    ///
1005    /// Java's LitePull consumer allows `subscribe`, `assign`, and
1006    /// `setSubExpressionForAssign` while the implementation is still in
1007    /// `CREATE_JUST`. Those calls populate local subscription state and only
1008    /// trigger broker route updates after `start()`.
1009    async fn get_or_init_impl(&self) -> RocketMQResult<&ArcMut<DefaultLitePullConsumerImpl>> {
1010        self.default_lite_pull_consumer_impl
1011            .get_or_try_init(|| async {
1012                let mut impl_ = ArcMut::new(DefaultLitePullConsumerImpl::new(
1013                    self.client_config.clone(),
1014                    self.consumer_config.clone(),
1015                ));
1016                let wrapper = impl_.clone();
1017                impl_.set_default_lite_pull_consumer_impl(wrapper);
1018                impl_.set_rpc_hook(self.rpc_hook.clone());
1019                impl_.set_message_queue_listener(self.current_message_queue_listener());
1020
1021                self.init_trace_dispatcher_internal(&impl_).await?;
1022
1023                impl_.set_consumer_group(self.consumer_group_with_namespace())?;
1024                if let Some(offset_store) = self.current_offset_store() {
1025                    impl_.mut_from_ref().set_offset_store(Some(offset_store))?;
1026                }
1027
1028                Ok::<ArcMut<DefaultLitePullConsumerImpl>, rocketmq_error::RocketMQError>(impl_)
1029            })
1030            .await
1031    }
1032
1033    fn current_message_queue_listener(&self) -> Option<ArcMessageQueueListener> {
1034        self.message_queue_listener
1035            .read()
1036            .ok()
1037            .and_then(|listener| listener.clone())
1038    }
1039
1040    fn set_message_queue_listener_local(&self, listener: Option<ArcMessageQueueListener>) {
1041        match self.message_queue_listener.write() {
1042            Ok(mut current) => *current = listener,
1043            Err(error) => tracing::warn!("LitePull message queue listener lock poisoned: {}", error),
1044        }
1045    }
1046
1047    fn current_offset_store(&self) -> Option<ArcMut<OffsetStore>> {
1048        self.offset_store.read().ok().and_then(|store| store.clone())
1049    }
1050
1051    fn set_offset_store_local(&self, offset_store: Option<ArcMut<OffsetStore>>) {
1052        match self.offset_store.write() {
1053            Ok(mut current) => *current = offset_store,
1054            Err(error) => tracing::warn!("LitePull offset store lock poisoned: {}", error),
1055        }
1056    }
1057}
1058
1059#[allow(unused)]
1060impl LitePullConsumer for DefaultLitePullConsumer {
1061    async fn start(&self) -> RocketMQResult<()> {
1062        let impl_ = self.get_or_init_impl().await?;
1063
1064        impl_.mut_from_ref().start().await?;
1065        self.set_offset_store_local(impl_.offset_store());
1066        self.start_trace_dispatcher().await;
1067        Ok(())
1068    }
1069
1070    async fn shutdown(&self) {
1071        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1072            let _ = impl_.mut_from_ref().shutdown().await;
1073        }
1074
1075        // Shutdown trace dispatcher
1076        if let Some(dispatcher) = self.trace_dispatcher.write().await.take() {
1077            dispatcher.shutdown();
1078        }
1079    }
1080
1081    async fn is_running(&self) -> bool {
1082        match self.default_lite_pull_consumer_impl.get() {
1083            Some(impl_) => impl_.is_running().await,
1084            None => false,
1085        }
1086    }
1087
1088    async fn subscribe(&self, topic: &str) -> RocketMQResult<()> {
1089        self.subscribe_with_expression(topic, "*").await
1090    }
1091
1092    async fn subscribe_with_expression(&self, topic: &str, sub_expression: &str) -> RocketMQResult<()> {
1093        let wrapped_topic = self.with_namespace(topic);
1094        self.get_or_init_impl()
1095            .await?
1096            .mut_from_ref()
1097            .subscribe(wrapped_topic, sub_expression)
1098            .await
1099    }
1100
1101    async fn subscribe_with_listener<MQL>(&self, topic: &str, sub_expression: &str, listener: MQL) -> RocketMQResult<()>
1102    where
1103        MQL: MessageQueueListener + 'static,
1104    {
1105        let wrapped_topic = self.with_namespace(topic);
1106        let listener: ArcMessageQueueListener = Arc::new(listener);
1107        self.get_or_init_impl()
1108            .await?
1109            .mut_from_ref()
1110            .subscribe_with_listener_arc(wrapped_topic, sub_expression, listener.clone())
1111            .await?;
1112        self.set_message_queue_listener_local(Some(listener));
1113        Ok(())
1114    }
1115
1116    async fn subscribe_with_selector(&self, topic: &str, selector: Option<MessageSelector>) -> RocketMQResult<()> {
1117        let wrapped_topic = self.with_namespace(topic);
1118        self.get_or_init_impl()
1119            .await?
1120            .mut_from_ref()
1121            .subscribe_with_selector(wrapped_topic, selector)
1122            .await
1123    }
1124
1125    async fn unsubscribe(&self, topic: &str) {
1126        let wrapped_topic = self.with_namespace(topic);
1127        match self.try_impl_() {
1128            Ok(impl_) => {
1129                if let Err(e) = impl_.mut_from_ref().unsubscribe(wrapped_topic).await {
1130                    tracing::warn!("Failed to unsubscribe from topic {}: {}", topic, e);
1131                }
1132            }
1133            Err(e) => tracing::warn!("Failed to unsubscribe from topic {}: {}", topic, e),
1134        }
1135    }
1136
1137    async fn assignment(&self) -> RocketMQResult<HashSet<MessageQueue>> {
1138        let assignment = self.try_impl_()?.assignment().await;
1139
1140        // Remove namespace from all queues
1141        let mut result = HashSet::with_capacity(assignment.len());
1142        for mq in assignment {
1143            result.insert(self.queue_without_namespace(&mq));
1144        }
1145
1146        Ok(result)
1147    }
1148
1149    async fn assign(&self, message_queues: Vec<MessageQueue>) -> RocketMQResult<()> {
1150        // Wrap namespace for all queues
1151        let wrapped_queues: Vec<_> = message_queues.iter().map(|mq| self.queue_with_namespace(mq)).collect();
1152
1153        self.get_or_init_impl()
1154            .await?
1155            .mut_from_ref()
1156            .assign(wrapped_queues)
1157            .await
1158    }
1159
1160    async fn set_sub_expression_for_assign(&self, topic: &str, sub_expression: &str) -> RocketMQResult<()> {
1161        let wrapped_topic = self.with_namespace(topic);
1162        self.get_or_init_impl()
1163            .await?
1164            .set_sub_expression_for_assign(wrapped_topic, sub_expression)
1165            .await
1166    }
1167
1168    async fn build_subscriptions_for_heartbeat(
1169        &self,
1170        sub_expression_map: &mut HashMap<String, MessageSelector>,
1171    ) -> RocketMQResult<()> {
1172        self.try_impl_()?
1173            .build_subscriptions_for_heartbeat(sub_expression_map)
1174            .await
1175    }
1176
1177    async fn subscriptions_for_heartbeat(&self) -> HashSet<SubscriptionData> {
1178        self.default_lite_pull_consumer_impl
1179            .get()
1180            .map_or_else(HashSet::new, |impl_| impl_.subscriptions_for_heartbeat())
1181    }
1182
1183    /// Zero-copy implementation.
1184    async fn poll_zero_copy(&self) -> Vec<ArcMut<MessageExt>> {
1185        match self.try_impl_() {
1186            Ok(impl_) => match impl_.poll(self.consumer_config.poll_timeout_millis).await {
1187                Ok(messages) => messages,
1188                Err(e) => {
1189                    tracing::error!("Poll failed: {}", e);
1190                    Vec::new()
1191                }
1192            },
1193            Err(e) => {
1194                tracing::error!("Poll failed: {}", e);
1195                Vec::new()
1196            }
1197        }
1198    }
1199
1200    /// Zero-copy implementation with custom timeout.
1201    async fn poll_with_timeout_zero_copy(&self, timeout: u64) -> Vec<ArcMut<MessageExt>> {
1202        match self.try_impl_() {
1203            Ok(impl_) => match impl_.poll_with_timeout(timeout).await {
1204                Ok(messages) => messages,
1205                Err(e) => {
1206                    tracing::error!("Poll failed: {}", e);
1207                    Vec::new()
1208                }
1209            },
1210            Err(e) => {
1211                tracing::error!("Poll failed: {}", e);
1212                Vec::new()
1213            }
1214        }
1215    }
1216
1217    /// Delegates to zero-copy implementation and creates owned message copies.
1218    async fn poll(&self) -> Vec<MessageExt> {
1219        self.poll_zero_copy()
1220            .await
1221            .into_iter()
1222            .map(|arc_mut| (*arc_mut).clone())
1223            .collect()
1224    }
1225
1226    /// Delegates to zero-copy implementation with timeout and creates owned message copies.
1227    async fn poll_with_timeout(&self, timeout: u64) -> Vec<MessageExt> {
1228        self.poll_with_timeout_zero_copy(timeout)
1229            .await
1230            .into_iter()
1231            .map(|arc_mut| (*arc_mut).clone())
1232            .collect()
1233    }
1234
1235    async fn message_model(&self) -> MessageModel {
1236        self.consumer_config.message_model
1237    }
1238
1239    async fn set_message_model(&self, message_model: MessageModel) {
1240        self.consumer_config.mut_from_ref().message_model = message_model;
1241        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1242            impl_.set_message_model(message_model);
1243        }
1244    }
1245
1246    async fn consume_from_where(&self) -> ConsumeFromWhere {
1247        self.consumer_config.consume_from_where
1248    }
1249
1250    async fn set_consume_from_where(&self, consume_from_where: ConsumeFromWhere) -> RocketMQResult<()> {
1251        validate_lite_pull_consume_from_where(consume_from_where)?;
1252        self.consumer_config.mut_from_ref().consume_from_where = consume_from_where;
1253        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1254            impl_.set_consume_from_where(consume_from_where)?;
1255        }
1256        Ok(())
1257    }
1258
1259    async fn consume_timestamp(&self) -> Option<CheetahString> {
1260        self.consumer_config.consume_timestamp.clone()
1261    }
1262
1263    async fn set_consume_timestamp(&self, consume_timestamp: Option<CheetahString>) {
1264        self.consumer_config.mut_from_ref().consume_timestamp = consume_timestamp.clone();
1265        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1266            impl_.set_consume_timestamp(consume_timestamp);
1267        }
1268    }
1269
1270    async fn allocate_message_queue_strategy(&self) -> Arc<dyn AllocateMessageQueueStrategy + Send + Sync> {
1271        self.default_lite_pull_consumer_impl.get().map_or_else(
1272            || self.consumer_config.allocate_message_queue_strategy.clone(),
1273            |impl_| impl_.allocate_message_queue_strategy(),
1274        )
1275    }
1276
1277    async fn set_allocate_message_queue_strategy(
1278        &self,
1279        allocate_message_queue_strategy: Arc<dyn AllocateMessageQueueStrategy + Send + Sync>,
1280    ) {
1281        self.consumer_config.mut_from_ref().allocate_message_queue_strategy = allocate_message_queue_strategy.clone();
1282        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1283            impl_.set_allocate_message_queue_strategy(allocate_message_queue_strategy);
1284        }
1285    }
1286
1287    async fn message_queue_listener(&self) -> Option<ArcMessageQueueListener> {
1288        self.default_lite_pull_consumer_impl
1289            .get()
1290            .and_then(|impl_| impl_.message_queue_listener())
1291            .or_else(|| self.current_message_queue_listener())
1292    }
1293
1294    async fn set_message_queue_listener(&self, message_queue_listener: Option<ArcMessageQueueListener>) {
1295        self.set_message_queue_listener_local(message_queue_listener.clone());
1296        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1297            impl_.set_message_queue_listener(message_queue_listener);
1298        }
1299    }
1300
1301    async fn offset_store(&self) -> Option<ArcMut<OffsetStore>> {
1302        self.default_lite_pull_consumer_impl
1303            .get()
1304            .and_then(|impl_| impl_.offset_store())
1305            .or_else(|| self.current_offset_store())
1306    }
1307
1308    async fn set_offset_store(&self, offset_store: Option<ArcMut<OffsetStore>>) -> RocketMQResult<()> {
1309        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1310            impl_.mut_from_ref().set_offset_store(offset_store.clone())?;
1311        }
1312        self.set_offset_store_local(offset_store);
1313        Ok(())
1314    }
1315
1316    async fn topic_metadata_check_interval_millis(&self) -> u64 {
1317        self.consumer_config.topic_metadata_check_interval_millis
1318    }
1319
1320    async fn set_topic_metadata_check_interval_millis(&self, interval_millis: u64) {
1321        self.consumer_config.mut_from_ref().topic_metadata_check_interval_millis = interval_millis;
1322        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1323            impl_.set_topic_metadata_check_interval_millis(interval_millis);
1324        }
1325    }
1326
1327    async fn poll_timeout_millis(&self) -> u64 {
1328        self.consumer_config.poll_timeout_millis
1329    }
1330
1331    async fn set_poll_timeout_millis(&self, timeout_millis: u64) {
1332        self.consumer_config.mut_from_ref().poll_timeout_millis = timeout_millis;
1333        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1334            impl_.set_poll_timeout_millis(timeout_millis);
1335        }
1336    }
1337
1338    async fn broker_suspend_max_time_millis(&self) -> u64 {
1339        self.consumer_config.broker_suspend_max_time_millis
1340    }
1341
1342    async fn set_broker_suspend_max_time_millis(&self, timeout_millis: u64) {
1343        self.consumer_config.mut_from_ref().broker_suspend_max_time_millis = timeout_millis;
1344        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1345            impl_.set_broker_suspend_max_time_millis(timeout_millis);
1346        }
1347    }
1348
1349    async fn consumer_timeout_millis_when_suspend(&self) -> u64 {
1350        self.consumer_config.consumer_timeout_millis_when_suspend
1351    }
1352
1353    async fn set_consumer_timeout_millis_when_suspend(&self, timeout_millis: u64) {
1354        self.consumer_config.mut_from_ref().consumer_timeout_millis_when_suspend = timeout_millis;
1355        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1356            impl_.set_consumer_timeout_millis_when_suspend(timeout_millis);
1357        }
1358    }
1359
1360    async fn consumer_pull_timeout_millis(&self) -> u64 {
1361        self.consumer_config.consumer_pull_timeout_millis
1362    }
1363
1364    async fn set_consumer_pull_timeout_millis(&self, timeout_millis: u64) {
1365        self.consumer_config.mut_from_ref().consumer_pull_timeout_millis = timeout_millis;
1366        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1367            impl_.set_consumer_pull_timeout_millis(timeout_millis);
1368        }
1369    }
1370
1371    async fn pull_batch_size(&self) -> i32 {
1372        self.consumer_config.pull_batch_size
1373    }
1374
1375    async fn set_pull_batch_size(&self, pull_batch_size: i32) {
1376        self.consumer_config.mut_from_ref().pull_batch_size = pull_batch_size;
1377        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1378            impl_.set_pull_batch_size(pull_batch_size);
1379        }
1380    }
1381
1382    async fn pull_thread_nums(&self) -> usize {
1383        self.consumer_config.pull_thread_nums
1384    }
1385
1386    async fn set_pull_thread_nums(&self, pull_thread_nums: usize) {
1387        self.consumer_config.mut_from_ref().pull_thread_nums = pull_thread_nums;
1388        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1389            impl_.set_pull_thread_nums(pull_thread_nums);
1390        }
1391    }
1392
1393    async fn pull_threshold_for_all(&self) -> i64 {
1394        self.consumer_config.pull_threshold_for_all
1395    }
1396
1397    async fn set_pull_threshold_for_all(&self, pull_threshold_for_all: i64) {
1398        self.consumer_config.mut_from_ref().pull_threshold_for_all = pull_threshold_for_all;
1399        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1400            impl_.set_pull_threshold_for_all(pull_threshold_for_all);
1401        }
1402    }
1403
1404    async fn pull_threshold_for_queue(&self) -> i64 {
1405        self.consumer_config.pull_threshold_for_queue
1406    }
1407
1408    async fn set_pull_threshold_for_queue(&self, pull_threshold_for_queue: i64) {
1409        self.consumer_config.mut_from_ref().pull_threshold_for_queue = pull_threshold_for_queue;
1410        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1411            impl_.set_pull_threshold_for_queue(pull_threshold_for_queue);
1412        }
1413    }
1414
1415    async fn pull_threshold_size_for_queue(&self) -> i32 {
1416        self.consumer_config.pull_threshold_size_for_queue
1417    }
1418
1419    async fn set_pull_threshold_size_for_queue(&self, pull_threshold_size_for_queue: i32) {
1420        self.consumer_config.mut_from_ref().pull_threshold_size_for_queue = pull_threshold_size_for_queue;
1421        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1422            impl_.set_pull_threshold_size_for_queue(pull_threshold_size_for_queue);
1423        }
1424    }
1425
1426    async fn consume_max_span(&self) -> i64 {
1427        self.consumer_config.consume_max_span
1428    }
1429
1430    async fn set_consume_max_span(&self, consume_max_span: i64) {
1431        self.consumer_config.mut_from_ref().consume_max_span = consume_max_span;
1432        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1433            impl_.set_consume_max_span(consume_max_span);
1434        }
1435    }
1436
1437    async fn is_connect_broker_by_user(&self) -> bool {
1438        self.default_lite_pull_consumer_impl
1439            .get()
1440            .map_or(self.consumer_config.connect_broker_by_user, |impl_| {
1441                impl_.is_connect_broker_by_user()
1442            })
1443    }
1444
1445    async fn set_connect_broker_by_user(&self, connect_broker_by_user: bool) {
1446        self.consumer_config.mut_from_ref().connect_broker_by_user = connect_broker_by_user;
1447        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1448            impl_.set_connect_broker_by_user(connect_broker_by_user);
1449        }
1450    }
1451
1452    async fn default_broker_id(&self) -> u64 {
1453        self.default_lite_pull_consumer_impl
1454            .get()
1455            .map_or(self.consumer_config.default_broker_id, |impl_| {
1456                impl_.default_broker_id()
1457            })
1458    }
1459
1460    async fn set_default_broker_id(&self, broker_id: u64) {
1461        self.consumer_config.mut_from_ref().default_broker_id = broker_id;
1462        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1463            impl_.set_default_broker_id(broker_id);
1464        }
1465    }
1466
1467    async fn fetch_message_queues(&self, topic: &str) -> RocketMQResult<Vec<MessageQueue>> {
1468        let wrapped_topic = self.with_namespace(topic);
1469        let queues = self.try_impl_()?.fetch_message_queues(wrapped_topic).await?;
1470
1471        // Remove namespace from all queues and convert to Vec
1472        let result: Vec<_> = queues.into_iter().map(|mq| self.queue_without_namespace(&mq)).collect();
1473
1474        Ok(result)
1475    }
1476
1477    async fn committed(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
1478        let wrapped_mq = self.queue_with_namespace(message_queue);
1479        self.try_impl_()?.committed(&wrapped_mq).await
1480    }
1481
1482    async fn commit_all(&self) -> RocketMQResult<()> {
1483        self.try_impl_()?.commit_all().await
1484    }
1485
1486    async fn commit_with_map(&self, offset_map: HashMap<MessageQueue, i64>, persist: bool) {
1487        // Wrap namespace for all queues
1488        let mut wrapped_offsets = HashMap::with_capacity(offset_map.len());
1489        for (mq, offset) in offset_map {
1490            wrapped_offsets.insert(self.queue_with_namespace(&mq), offset);
1491        }
1492
1493        match self.try_impl_() {
1494            Ok(impl_) => {
1495                if let Err(e) = impl_.commit(wrapped_offsets, persist).await {
1496                    tracing::warn!("commit_with_map failed: {}", e);
1497                }
1498            }
1499            Err(e) => tracing::warn!("commit_with_map failed: {}", e),
1500        }
1501    }
1502
1503    async fn commit(&self) {
1504        if let Err(e) = self.commit_all().await {
1505            tracing::warn!("commit failed: {}", e);
1506        }
1507    }
1508
1509    async fn commit_with_set(&self, message_queues: HashSet<MessageQueue>, persist: bool) {
1510        // Wrap namespace for all queues
1511        let wrapped_queues: HashSet<_> = message_queues.iter().map(|mq| self.queue_with_namespace(mq)).collect();
1512        let impl_ = match self.try_impl_() {
1513            Ok(impl_) => impl_,
1514            Err(e) => {
1515                tracing::warn!("commit_with_set failed: {}", e);
1516                return;
1517            }
1518        };
1519
1520        if let Err(e) = impl_.commit_message_queues(&wrapped_queues, persist).await {
1521            tracing::warn!("commit_with_set failed: {}", e);
1522        }
1523    }
1524
1525    async fn commit_sync(&self) {
1526        // Commit all offsets (trait note: misleading name, doesn't actually block)
1527        if let Err(e) = self.commit_all().await {
1528            tracing::warn!("commit_sync failed: {}", e);
1529        }
1530    }
1531
1532    async fn commit_sync_with_map(&self, offset_map: HashMap<MessageQueue, i64>, persist: bool) {
1533        // Commit specific offsets (trait note: misleading name, doesn't actually block)
1534        self.commit_with_map(offset_map, persist).await;
1535    }
1536
1537    async fn is_auto_commit(&self) -> bool {
1538        self.consumer_config.auto_commit
1539    }
1540
1541    async fn set_auto_commit(&self, auto_commit: bool) {
1542        self.consumer_config.mut_from_ref().auto_commit = auto_commit;
1543        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1544            impl_.set_auto_commit(auto_commit);
1545        }
1546    }
1547
1548    async fn is_unit_mode(&self) -> bool {
1549        self.consumer_config.unit_mode
1550    }
1551
1552    async fn set_unit_mode(&self, unit_mode: bool) {
1553        self.consumer_config.mut_from_ref().unit_mode = unit_mode;
1554        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1555            impl_.set_unit_mode(unit_mode);
1556        }
1557    }
1558
1559    async fn auto_commit_interval_millis(&self) -> u64 {
1560        self.consumer_config.auto_commit_interval_millis
1561    }
1562
1563    async fn set_auto_commit_interval_millis(&self, interval_millis: u64) {
1564        if interval_millis < MIN_AUTOCOMMIT_INTERVAL_MILLIS {
1565            return;
1566        }
1567        self.consumer_config.mut_from_ref().auto_commit_interval_millis = interval_millis;
1568        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1569            impl_.set_auto_commit_interval_millis(interval_millis);
1570        }
1571    }
1572
1573    async fn offset_for_timestamp(&self, message_queue: &MessageQueue, timestamp: u64) -> RocketMQResult<i64> {
1574        let wrapped_mq = self.queue_with_namespace(message_queue);
1575        self.try_impl_()?.offset_for_timestamp(&wrapped_mq, timestamp).await
1576    }
1577
1578    async fn earliest_msg_store_time(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
1579        let wrapped_mq = self.queue_with_namespace(message_queue);
1580        self.try_impl_()?.earliest_msg_store_time(&wrapped_mq).await
1581    }
1582
1583    async fn max_offset(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
1584        let wrapped_mq = self.queue_with_namespace(message_queue);
1585        self.try_impl_()?.max_offset_public(&wrapped_mq).await
1586    }
1587
1588    async fn min_offset(&self, message_queue: &MessageQueue) -> RocketMQResult<i64> {
1589        let wrapped_mq = self.queue_with_namespace(message_queue);
1590        self.try_impl_()?.min_offset_public(&wrapped_mq).await
1591    }
1592
1593    async fn pause(&self, message_queues: Vec<MessageQueue>) {
1594        let wrapped_queues: Vec<_> = message_queues.iter().map(|mq| self.queue_with_namespace(mq)).collect();
1595
1596        match self.try_impl_() {
1597            Ok(impl_) => impl_.pause(&wrapped_queues).await,
1598            Err(e) => tracing::warn!("pause failed: {}", e),
1599        }
1600    }
1601
1602    async fn resume(&self, message_queues: Vec<MessageQueue>) {
1603        let wrapped_queues: Vec<_> = message_queues.iter().map(|mq| self.queue_with_namespace(mq)).collect();
1604
1605        match self.try_impl_() {
1606            Ok(impl_) => impl_.resume(&wrapped_queues).await,
1607            Err(e) => tracing::warn!("resume failed: {}", e),
1608        }
1609    }
1610
1611    async fn is_paused(&self, message_queue: &MessageQueue) -> bool {
1612        let wrapped_mq = self.queue_with_namespace(message_queue);
1613        match self.try_impl_() {
1614            Ok(impl_) => impl_.is_paused(&wrapped_mq).await,
1615            Err(e) => {
1616                tracing::warn!("is_paused failed: {}", e);
1617                false
1618            }
1619        }
1620    }
1621
1622    async fn seek(&self, message_queue: &MessageQueue, offset: i64) -> RocketMQResult<()> {
1623        let wrapped_mq = self.queue_with_namespace(message_queue);
1624        self.try_impl_()?.seek(&wrapped_mq, offset).await
1625    }
1626
1627    async fn seek_to_begin(&self, message_queue: &MessageQueue) -> RocketMQResult<()> {
1628        let wrapped_mq = self.queue_with_namespace(message_queue);
1629        self.try_impl_()?.seek_to_begin(&wrapped_mq).await
1630    }
1631
1632    async fn seek_to_end(&self, message_queue: &MessageQueue) -> RocketMQResult<()> {
1633        let wrapped_mq = self.queue_with_namespace(message_queue);
1634        self.try_impl_()?.seek_to_end(&wrapped_mq).await
1635    }
1636
1637    async fn update_name_server_address(&self, name_server_address: &str) {
1638        self.client_config
1639            .mut_from_ref()
1640            .set_namesrv_addr(CheetahString::from_string(name_server_address.to_string()));
1641
1642        if let Some(impl_) = self.default_lite_pull_consumer_impl.get() {
1643            let addresses: Vec<String> = name_server_address
1644                .split(';')
1645                .map(|s| s.trim().to_string())
1646                .filter(|s| !s.is_empty())
1647                .collect();
1648            impl_.update_name_server_address(addresses).await;
1649        }
1650    }
1651
1652    async fn register_topic_message_queue_change_listener<TL>(&self, topic: &str, listener: TL) -> RocketMQResult<()>
1653    where
1654        TL: TopicMessageQueueChangeListener + 'static,
1655    {
1656        let wrapped_topic = self.with_namespace(topic);
1657        let listener_arc: Arc<dyn TopicMessageQueueChangeListener + Send + Sync> = Arc::new(listener);
1658        self.try_impl_()?
1659            .register_topic_message_queue_change_listener(wrapped_topic, listener_arc)
1660            .await
1661    }
1662}
1663
1664impl MQConsumer for DefaultLitePullConsumer {
1665    async fn create_topic(
1666        &mut self,
1667        key: &str,
1668        new_topic: &str,
1669        queue_num: i32,
1670        attributes: HashMap<String, String>,
1671    ) -> RocketMQResult<()> {
1672        DefaultLitePullConsumer::create_topic(self, key, new_topic, queue_num, attributes).await
1673    }
1674
1675    async fn create_topic_with_flag(
1676        &mut self,
1677        key: &str,
1678        new_topic: &str,
1679        queue_num: i32,
1680        topic_sys_flag: i32,
1681        attributes: HashMap<String, String>,
1682    ) -> RocketMQResult<()> {
1683        DefaultLitePullConsumer::create_topic_with_flag(self, key, new_topic, queue_num, topic_sys_flag, attributes)
1684            .await
1685    }
1686
1687    async fn query_message(
1688        &mut self,
1689        topic: &str,
1690        key: &str,
1691        max_num: i32,
1692        begin: u64,
1693        end: u64,
1694    ) -> RocketMQResult<QueryResult> {
1695        DefaultLitePullConsumer::query_message(self, topic, key, max_num, begin, end).await
1696    }
1697
1698    async fn view_message(&mut self, topic: &str, msg_id: &str) -> RocketMQResult<MessageExt> {
1699        DefaultLitePullConsumer::view_message(self, topic, msg_id).await
1700    }
1701
1702    async fn send_message_back(&mut self, msg: MessageExt, delay_level: i32, broker_name: &str) -> RocketMQResult<()> {
1703        // Lite pull consumer doesn't support send message back
1704        // This is typically used in push consumer for retry
1705        Err(crate::mq_client_err!(
1706            -1,
1707            "sendMessageBack is not supported in lite pull consumer"
1708        ))
1709    }
1710
1711    async fn fetch_subscribe_message_queues(&mut self, topic: &str) -> RocketMQResult<Vec<MessageQueue>> {
1712        let queues = self.fetch_message_queues(topic).await?;
1713        Ok(queues.into_iter().collect())
1714    }
1715
1716    async fn search_offset(&mut self, mq: &MessageQueue, timestamp: u64) -> RocketMQResult<i64> {
1717        <Self as LitePullConsumer>::offset_for_timestamp(self, mq, timestamp).await
1718    }
1719
1720    async fn max_offset(&mut self, mq: &MessageQueue) -> RocketMQResult<i64> {
1721        <Self as LitePullConsumer>::max_offset(self, mq).await
1722    }
1723
1724    async fn min_offset(&mut self, mq: &MessageQueue) -> RocketMQResult<i64> {
1725        <Self as LitePullConsumer>::min_offset(self, mq).await
1726    }
1727
1728    async fn earliest_msg_store_time(&mut self, mq: &MessageQueue) -> RocketMQResult<i64> {
1729        <Self as LitePullConsumer>::earliest_msg_store_time(self, mq).await
1730    }
1731}
1732
1733#[cfg(test)]
1734mod tests {
1735    use std::any::Any;
1736    use std::sync::atomic::AtomicUsize;
1737    use std::sync::atomic::Ordering;
1738    use std::sync::Mutex as StdMutex;
1739
1740    use super::*;
1741    use crate::base::access_channel::AccessChannel;
1742    use crate::common::acl_client_rpc_hook::AclClientRPCHook;
1743    use crate::common::session_credentials::SessionCredentials;
1744    use crate::consumer::mq_consumer_inner::MQConsumerInner;
1745    use crate::consumer::rebalance_strategy::allocate_message_queue_averagely_by_circle::AllocateMessageQueueAveragelyByCircle;
1746    use crate::consumer::store::read_offset_type::ReadOffsetType;
1747
1748    #[derive(Default)]
1749    struct CapturingTraceDispatcher {
1750        start_count: AtomicUsize,
1751        shutdown_count: AtomicUsize,
1752        append_count: AtomicUsize,
1753        last_name_srv_addr: StdMutex<Option<String>>,
1754        last_access_channel: StdMutex<Option<AccessChannel>>,
1755    }
1756
1757    impl TraceDispatcher for CapturingTraceDispatcher {
1758        fn start(&self, name_srv_addr: &str, access_channel: AccessChannel) -> RocketMQResult<()> {
1759            self.start_count.fetch_add(1, Ordering::SeqCst);
1760            *self.last_name_srv_addr.lock().expect("name server lock") = Some(name_srv_addr.to_string());
1761            *self.last_access_channel.lock().expect("access channel lock") = Some(access_channel);
1762            Ok(())
1763        }
1764
1765        fn append(&self, _ctx: &dyn Any) -> bool {
1766            self.append_count.fetch_add(1, Ordering::SeqCst);
1767            true
1768        }
1769
1770        fn flush(&self) -> RocketMQResult<()> {
1771            Ok(())
1772        }
1773
1774        fn shutdown(&self) {
1775            self.shutdown_count.fetch_add(1, Ordering::SeqCst);
1776        }
1777
1778        fn as_any(&self) -> &dyn Any {
1779            self
1780        }
1781
1782        fn as_mut_any(&mut self) -> &mut dyn Any {
1783            self
1784        }
1785    }
1786
1787    struct NoopMessageQueueListener;
1788
1789    impl MessageQueueListener for NoopMessageQueueListener {
1790        fn message_queue_changed(
1791            &self,
1792            _topic: &str,
1793            _mq_all: &HashSet<MessageQueue>,
1794            _mq_assigned: &HashSet<MessageQueue>,
1795        ) {
1796        }
1797    }
1798
1799    fn new_unstarted_consumer() -> DefaultLitePullConsumer {
1800        DefaultLitePullConsumer::builder()
1801            .consumer_group("lite_pull_group")
1802            .build()
1803            .expect("builder should create consumer")
1804    }
1805
1806    fn new_namespaced_consumer_with_impl() -> (DefaultLitePullConsumer, ArcMut<DefaultLitePullConsumerImpl>) {
1807        let consumer = DefaultLitePullConsumer::builder()
1808            .consumer_group("lite_pull_namespace_group")
1809            .namespace("ns")
1810            .build()
1811            .expect("builder should create namespaced consumer");
1812        let mut impl_ = ArcMut::new(DefaultLitePullConsumerImpl::new(
1813            consumer.client_config.clone(),
1814            consumer.consumer_config.clone(),
1815        ));
1816        let wrapper = impl_.clone();
1817        impl_.set_default_lite_pull_consumer_impl(wrapper);
1818        if consumer.default_lite_pull_consumer_impl.set(impl_.clone()).is_err() {
1819            panic!("test consumer impl should be set once");
1820        }
1821
1822        (consumer, impl_)
1823    }
1824
1825    #[tokio::test]
1826    async fn assignment_before_start_returns_not_initialized_error() {
1827        let consumer = new_unstarted_consumer();
1828
1829        let error = consumer
1830            .assignment()
1831            .await
1832            .expect_err("assignment before start should fail");
1833
1834        assert!(error.to_string().contains("DefaultLitePullConsumer not started"));
1835    }
1836
1837    #[tokio::test]
1838    async fn consumer_running_info_before_start_returns_not_initialized_error() {
1839        let consumer = new_unstarted_consumer();
1840
1841        let error = consumer
1842            .consumer_running_info()
1843            .await
1844            .expect_err("running info before start should fail");
1845
1846        assert!(error.to_string().contains("DefaultLitePullConsumer not started"));
1847    }
1848
1849    #[tokio::test]
1850    async fn subscribe_before_start_initializes_impl_and_records_subscription_like_java() {
1851        let consumer = DefaultLitePullConsumer::builder()
1852            .consumer_group("lite_pull_pre_start_subscribe_group")
1853            .namespace("ns")
1854            .build()
1855            .expect("builder should create consumer");
1856
1857        consumer
1858            .subscribe_with_expression("TopicPreStart", "TagA")
1859            .await
1860            .expect("LitePull should accept subscribe before start like Java");
1861
1862        assert!(!consumer.is_running().await);
1863        let impl_ = consumer
1864            .try_impl_()
1865            .expect("pre-start subscribe should initialize the implementation");
1866        let subscriptions = impl_.subscriptions();
1867        let subscription = subscriptions
1868            .iter()
1869            .find(|subscription| subscription.topic.as_str() == "ns%TopicPreStart")
1870            .expect("pre-start subscribe should be stored in rebalance subscriptions");
1871
1872        assert_eq!(subscription.sub_string.as_str(), "TagA");
1873    }
1874
1875    #[tokio::test]
1876    async fn initialization_propagates_rpc_hook_to_impl() {
1877        let rpc_hook = Arc::new(AclClientRPCHook::new(SessionCredentials::with_keys(
1878            "access_key",
1879            "secret_key",
1880        )));
1881        let consumer = DefaultLitePullConsumer::builder()
1882            .consumer_group("lite_pull_rpc_hook_group")
1883            .rpc_hook(rpc_hook)
1884            .build()
1885            .expect("builder should create consumer with RPC hook");
1886
1887        let impl_ = consumer
1888            .get_or_init_impl()
1889            .await
1890            .expect("impl initialization should keep RPC hook");
1891
1892        assert!(impl_.has_rpc_hook(), "LitePull impl should retain builder RPC hook");
1893    }
1894
1895    #[tokio::test]
1896    async fn mq_consumer_offset_facade_delegates_to_lite_pull_impl_path() {
1897        let mut consumer = new_unstarted_consumer();
1898        let mq = MessageQueue::from_parts("TopicLite", "broker-a", 0);
1899
1900        let search_error = <DefaultLitePullConsumer as MQConsumer>::search_offset(&mut consumer, &mq, 1)
1901            .await
1902            .expect_err("searchOffset should enter LitePull path and fail because the consumer is not started");
1903        let max_error = <DefaultLitePullConsumer as MQConsumer>::max_offset(&mut consumer, &mq)
1904            .await
1905            .expect_err("maxOffset should enter LitePull path and fail because the consumer is not started");
1906        let min_error = <DefaultLitePullConsumer as MQConsumer>::min_offset(&mut consumer, &mq)
1907            .await
1908            .expect_err("minOffset should enter LitePull path and fail because the consumer is not started");
1909        let earliest_error = <DefaultLitePullConsumer as MQConsumer>::earliest_msg_store_time(&mut consumer, &mq)
1910            .await
1911            .expect_err("earliestMsgStoreTime should enter LitePull path and fail because the consumer is not started");
1912
1913        for error in [search_error, max_error, min_error, earliest_error] {
1914            let message = error.to_string();
1915            assert!(message.contains("DefaultLitePullConsumer not started"));
1916            assert!(!message.contains("not supported by this MQConsumer implementation"));
1917        }
1918    }
1919
1920    #[tokio::test]
1921    async fn mq_consumer_admin_facade_delegates_to_lite_pull_impl_path() {
1922        let mut consumer = new_unstarted_consumer();
1923
1924        let create_error = <DefaultLitePullConsumer as MQConsumer>::create_topic(
1925            &mut consumer,
1926            TopicValidator::AUTO_CREATE_TOPIC_KEY_TOPIC,
1927            "TopicLiteAdmin",
1928            1,
1929            HashMap::new(),
1930        )
1931        .await
1932        .expect_err("createTopic should enter LitePull path and fail because the consumer is not started");
1933        let create_with_flag_error = <DefaultLitePullConsumer as MQConsumer>::create_topic_with_flag(
1934            &mut consumer,
1935            TopicValidator::AUTO_CREATE_TOPIC_KEY_TOPIC,
1936            "TopicLiteAdmin",
1937            1,
1938            0,
1939            HashMap::new(),
1940        )
1941        .await
1942        .expect_err("createTopicWithFlag should enter LitePull path and fail because the consumer is not started");
1943        let query_error =
1944            <DefaultLitePullConsumer as MQConsumer>::query_message(&mut consumer, "TopicLiteAdmin", "key", 32, 0, 1)
1945                .await
1946                .expect_err("queryMessage should enter LitePull path and fail because the consumer is not started");
1947        let view_error =
1948            <DefaultLitePullConsumer as MQConsumer>::view_message(&mut consumer, "TopicLiteAdmin", "not-a-msg-id")
1949                .await
1950                .expect_err("viewMessage should enter LitePull path and fail because the consumer is not started");
1951
1952        for error in [create_error, create_with_flag_error, query_error, view_error] {
1953            let message = error.to_string();
1954            assert!(message.contains("DefaultLitePullConsumer not started"));
1955            assert!(!message.contains("not supported by this MQConsumer implementation"));
1956        }
1957    }
1958
1959    #[tokio::test]
1960    async fn consumer_running_info_facade_returns_lite_pull_snapshot() {
1961        let (consumer, _impl_) = new_namespaced_consumer_with_impl();
1962        consumer
1963            .subscribe_with_expression("TopicLiteRunningInfo", "TagA")
1964            .await
1965            .expect("subscribe should update running-info subscriptions");
1966        consumer.set_pull_batch_size(17).await;
1967        consumer.set_auto_commit(false).await;
1968        consumer.set_auto_commit_interval_millis(2_000).await;
1969
1970        let info = consumer
1971            .consumer_running_info()
1972            .await
1973            .expect("running info facade should delegate to impl");
1974
1975        assert_eq!(
1976            info.properties
1977                .get(ConsumerRunningInfo::PROP_CONSUME_TYPE)
1978                .map(String::as_str),
1979            Some("CONSUME_ACTIVELY")
1980        );
1981        assert_eq!(
1982            info.properties.get("consumerGroup").map(String::as_str),
1983            Some("lite_pull_namespace_group")
1984        );
1985        assert_eq!(info.properties.get("pullBatchSize").map(String::as_str), Some("17"));
1986        assert_eq!(info.properties.get("autoCommit").map(String::as_str), Some("false"));
1987        assert_eq!(
1988            info.properties.get("autoCommitIntervalMillis").map(String::as_str),
1989            Some("2000")
1990        );
1991        assert!(info
1992            .subscription_set
1993            .iter()
1994            .any(|subscription| subscription.topic.as_str() == "ns%TopicLiteRunningInfo"
1995                && subscription.sub_string.as_str() == "TagA"));
1996    }
1997
1998    #[tokio::test]
1999    async fn assign_surfaces_impl_errors_like_java() {
2000        let (consumer, _impl_) = new_namespaced_consumer_with_impl();
2001
2002        let error = consumer
2003            .assign(Vec::new())
2004            .await
2005            .expect_err("empty manual assignment should be rejected");
2006
2007        assert!(error.to_string().contains("Message queues can not be null or empty."));
2008    }
2009
2010    #[tokio::test]
2011    async fn offset_metadata_queries_before_start_return_not_initialized_error() {
2012        let consumer = new_unstarted_consumer();
2013        let queue = MessageQueue::from_parts("TopicA", "broker-a", 0);
2014
2015        let earliest_error = consumer
2016            .earliest_msg_store_time(&queue)
2017            .await
2018            .expect_err("earliest store time before start should fail");
2019        let max_error = consumer
2020            .max_offset(&queue)
2021            .await
2022            .expect_err("max offset before start should fail");
2023        let min_error = consumer
2024            .min_offset(&queue)
2025            .await
2026            .expect_err("min offset before start should fail");
2027
2028        assert!(earliest_error
2029            .to_string()
2030            .contains("DefaultLitePullConsumer not started"));
2031        assert!(max_error.to_string().contains("DefaultLitePullConsumer not started"));
2032        assert!(min_error.to_string().contains("DefaultLitePullConsumer not started"));
2033    }
2034
2035    #[tokio::test]
2036    async fn poll_before_start_returns_empty_vec_without_panic() {
2037        let consumer = new_unstarted_consumer();
2038
2039        assert!(consumer.poll_zero_copy().await.is_empty());
2040        assert!(consumer.poll_with_timeout_zero_copy(1).await.is_empty());
2041    }
2042
2043    #[tokio::test]
2044    async fn lite_pull_facade_namespace_subscribe_wraps_builder_namespace_like_java() {
2045        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2046
2047        assert_eq!(consumer.namespace().as_deref(), Some("ns"));
2048        consumer
2049            .subscribe_with_expression("TopicA", "TagA")
2050            .await
2051            .expect("subscribe should succeed with injected impl");
2052
2053        let subscriptions = impl_.subscriptions();
2054        let subscription = subscriptions
2055            .iter()
2056            .find(|subscription| subscription.topic.as_str() == "ns%TopicA")
2057            .expect("namespaced topic should be subscribed");
2058        assert_eq!(subscription.sub_string.as_str(), "TagA");
2059        assert!(!subscriptions
2060            .iter()
2061            .any(|subscription| subscription.topic.as_str() == "TopicA"));
2062    }
2063
2064    #[tokio::test]
2065    async fn subscriptions_for_heartbeat_returns_current_rebalance_subscriptions_like_java_getter() {
2066        let (consumer, _impl_) = new_namespaced_consumer_with_impl();
2067
2068        assert!(consumer.subscriptions_for_heartbeat().await.is_empty());
2069
2070        consumer
2071            .subscribe_with_expression("TopicA", "TagA")
2072            .await
2073            .expect("subscribe should succeed with injected impl");
2074
2075        let subscriptions = consumer.subscriptions_for_heartbeat().await;
2076        assert_eq!(subscriptions.len(), 1);
2077        let subscription = subscriptions.iter().next().expect("subscription should exist");
2078        assert_eq!(subscription.topic.as_str(), "ns%TopicA");
2079        assert_eq!(subscription.sub_string.as_str(), "TagA");
2080    }
2081
2082    #[tokio::test]
2083    async fn lite_pull_java_getter_aliases_delegate_to_config() {
2084        let consumer = new_unstarted_consumer();
2085
2086        assert_eq!(consumer.get_message_model().await, consumer.message_model().await);
2087        assert_eq!(
2088            consumer.get_consume_from_where().await,
2089            consumer.consume_from_where().await
2090        );
2091        assert_eq!(
2092            consumer.get_consume_timestamp().await,
2093            consumer.consume_timestamp().await
2094        );
2095        assert_eq!(
2096            consumer.get_allocate_message_queue_strategy().await.get_name(),
2097            consumer.allocate_message_queue_strategy().await.get_name()
2098        );
2099        assert_eq!(
2100            consumer.get_offset_store().await.is_some(),
2101            consumer.offset_store().await.is_some()
2102        );
2103        assert_eq!(
2104            consumer.get_topic_metadata_check_interval_millis().await,
2105            consumer.topic_metadata_check_interval_millis().await
2106        );
2107        assert_eq!(
2108            consumer.get_poll_timeout_millis().await,
2109            consumer.poll_timeout_millis().await
2110        );
2111        assert_eq!(
2112            consumer.get_consumer_timeout_millis_when_suspend().await,
2113            consumer.consumer_timeout_millis_when_suspend().await
2114        );
2115        assert_eq!(
2116            consumer.get_consumer_pull_timeout_millis().await,
2117            consumer.consumer_pull_timeout_millis().await
2118        );
2119        assert_eq!(consumer.get_pull_batch_size().await, consumer.pull_batch_size().await);
2120        assert_eq!(
2121            consumer.get_pull_threshold_for_all().await,
2122            consumer.pull_threshold_for_all().await
2123        );
2124        assert_eq!(
2125            consumer.get_pull_threshold_for_queue().await,
2126            consumer.pull_threshold_for_queue().await
2127        );
2128        assert_eq!(
2129            consumer.get_pull_threshold_size_for_queue().await,
2130            consumer.pull_threshold_size_for_queue().await
2131        );
2132        assert_eq!(
2133            consumer.get_auto_commit_interval_millis().await,
2134            consumer.auto_commit_interval_millis().await
2135        );
2136    }
2137
2138    #[tokio::test]
2139    async fn set_sub_expression_for_assign_surfaces_impl_errors_like_java() {
2140        let (consumer, _impl_) = new_namespaced_consumer_with_impl();
2141
2142        let error = consumer
2143            .set_sub_expression_for_assign("TopicA", " ")
2144            .await
2145            .expect_err("blank assign filter should be rejected");
2146        assert!(error.to_string().contains("subExpression can not be null or empty."));
2147
2148        consumer
2149            .set_sub_expression_for_assign("TopicA", "TagA")
2150            .await
2151            .expect("valid assign filter should be accepted before start");
2152    }
2153
2154    #[tokio::test]
2155    async fn set_auto_commit_updates_initialized_impl_like_java_facade_reference() {
2156        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2157
2158        assert!(consumer.is_auto_commit().await);
2159        assert!(impl_.is_auto_commit());
2160
2161        consumer.set_auto_commit(false).await;
2162
2163        assert!(!consumer.is_auto_commit().await);
2164        assert!(!impl_.is_auto_commit());
2165
2166        consumer.set_auto_commit(true).await;
2167
2168        assert!(consumer.is_auto_commit().await);
2169        assert!(impl_.is_auto_commit());
2170    }
2171
2172    #[tokio::test]
2173    async fn set_auto_commit_interval_updates_initialized_impl_and_ignores_invalid_like_java() {
2174        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2175
2176        assert_eq!(consumer.auto_commit_interval_millis().await, 5000);
2177        assert_eq!(impl_.auto_commit_interval_millis(), 5000);
2178
2179        consumer.set_auto_commit_interval_millis(2000).await;
2180
2181        assert_eq!(consumer.auto_commit_interval_millis().await, 2000);
2182        assert_eq!(impl_.auto_commit_interval_millis(), 2000);
2183
2184        consumer
2185            .set_auto_commit_interval_millis(MIN_AUTOCOMMIT_INTERVAL_MILLIS - 1)
2186            .await;
2187
2188        assert_eq!(consumer.auto_commit_interval_millis().await, 2000);
2189        assert_eq!(impl_.auto_commit_interval_millis(), 2000);
2190    }
2191
2192    #[tokio::test]
2193    async fn set_unit_mode_updates_initialized_impl_and_heartbeat_source_like_java() {
2194        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2195
2196        assert!(!consumer.is_unit_mode().await);
2197        assert!(!impl_.is_unit_mode());
2198        assert!(!MQConsumerInner::is_unit_mode(impl_.as_ref()));
2199
2200        consumer.set_unit_mode(true).await;
2201
2202        assert!(consumer.is_unit_mode().await);
2203        assert!(impl_.is_unit_mode());
2204        assert!(MQConsumerInner::is_unit_mode(impl_.as_ref()));
2205    }
2206
2207    #[test]
2208    fn set_consumer_group_updates_initialized_impl_and_rebalance_before_start_like_java() {
2209        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2210
2211        consumer
2212            .set_consumer_group("lite_pull_updated_group")
2213            .expect("consumer group should be mutable before start");
2214
2215        assert_eq!(consumer.consumer_group().as_str(), "lite_pull_updated_group");
2216        assert_eq!(impl_.consumer_group_config().as_str(), "lite_pull_updated_group");
2217        assert_eq!(
2218            MQConsumerInner::group_name(impl_.as_ref()).as_str(),
2219            "lite_pull_updated_group"
2220        );
2221        assert_eq!(
2222            impl_.rebalance_consumer_group_name().as_deref(),
2223            Some("lite_pull_updated_group")
2224        );
2225    }
2226
2227    #[test]
2228    fn start_namespace_wraps_consumer_group_like_java_lite_pull_start() {
2229        let (consumer, _impl_) = new_namespaced_consumer_with_impl();
2230
2231        assert_eq!(
2232            consumer.consumer_group_with_namespace().as_str(),
2233            "ns%lite_pull_namespace_group"
2234        );
2235
2236        consumer
2237            .set_consumer_group("ns%lite_pull_namespace_group")
2238            .expect("pre-wrapped group should be accepted before start");
2239
2240        assert_eq!(
2241            consumer.consumer_group_with_namespace().as_str(),
2242            "ns%lite_pull_namespace_group"
2243        );
2244    }
2245
2246    #[tokio::test]
2247    async fn set_message_queue_listener_updates_initialized_impl_like_java() {
2248        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2249
2250        assert!(consumer.message_queue_listener().await.is_none());
2251        assert!(impl_.message_queue_listener().is_none());
2252
2253        let listener: ArcMessageQueueListener = Arc::new(NoopMessageQueueListener);
2254        consumer.set_message_queue_listener(Some(listener.clone())).await;
2255
2256        let facade_listener = consumer
2257            .message_queue_listener()
2258            .await
2259            .expect("facade should return configured listener");
2260        let impl_listener = impl_
2261            .message_queue_listener()
2262            .expect("impl should receive configured listener");
2263        assert!(Arc::ptr_eq(&facade_listener, &listener));
2264        assert!(Arc::ptr_eq(&impl_listener, &listener));
2265
2266        consumer.set_message_queue_listener(None).await;
2267
2268        assert!(consumer.message_queue_listener().await.is_none());
2269        assert!(impl_.message_queue_listener().is_none());
2270    }
2271
2272    #[tokio::test]
2273    async fn set_offset_store_updates_initialized_impl_and_rebalance_like_java() {
2274        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2275        let queue = MessageQueue::from_parts("TopicA", "broker-a", 0);
2276        let offset_store = ArcMut::new(OffsetStore::new_test());
2277
2278        assert!(consumer.offset_store().await.is_none());
2279        assert!(impl_.offset_store().is_none());
2280        assert!(!impl_.rebalance_has_offset_store());
2281
2282        consumer
2283            .set_offset_store(Some(offset_store.clone()))
2284            .await
2285            .expect("offset store should be mutable before start");
2286        offset_store.update_offset(&queue, 42, false).await;
2287
2288        let facade_store = consumer
2289            .offset_store()
2290            .await
2291            .expect("facade should expose configured offset store");
2292        let impl_store = impl_
2293            .offset_store()
2294            .expect("impl should receive configured offset store");
2295        assert_eq!(
2296            facade_store.read_offset(&queue, ReadOffsetType::ReadFromMemory).await,
2297            42
2298        );
2299        assert_eq!(impl_store.read_offset(&queue, ReadOffsetType::ReadFromMemory).await, 42);
2300        assert!(impl_.rebalance_has_offset_store());
2301
2302        consumer
2303            .set_offset_store(None)
2304            .await
2305            .expect("offset store should be clearable before start");
2306
2307        assert!(consumer.offset_store().await.is_none());
2308        assert!(impl_.offset_store().is_none());
2309        assert!(!impl_.rebalance_has_offset_store());
2310    }
2311
2312    #[tokio::test]
2313    async fn set_allocate_message_queue_strategy_updates_initialized_impl_and_rebalance_like_java() {
2314        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2315
2316        assert_eq!(consumer.allocate_message_queue_strategy().await.get_name(), "AVG");
2317        assert_eq!(impl_.allocate_message_queue_strategy().get_name(), "AVG");
2318        assert_eq!(impl_.rebalance_allocate_message_queue_strategy_name(), None);
2319
2320        consumer
2321            .set_allocate_message_queue_strategy(Arc::new(AllocateMessageQueueAveragelyByCircle))
2322            .await;
2323
2324        assert_eq!(
2325            consumer.allocate_message_queue_strategy().await.get_name(),
2326            "AVG_BY_CIRCLE"
2327        );
2328        assert_eq!(impl_.allocate_message_queue_strategy().get_name(), "AVG_BY_CIRCLE");
2329        assert_eq!(
2330            impl_.rebalance_allocate_message_queue_strategy_name(),
2331            Some("AVG_BY_CIRCLE")
2332        );
2333    }
2334
2335    #[tokio::test]
2336    #[allow(deprecated)]
2337    async fn core_runtime_config_setters_update_initialized_impl_like_java_facade_reference() {
2338        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2339
2340        assert_eq!(consumer.message_model().await, MessageModel::Clustering);
2341        assert_eq!(impl_.message_model_config(), MessageModel::Clustering);
2342        assert_eq!(MQConsumerInner::message_model(impl_.as_ref()), MessageModel::Clustering);
2343
2344        consumer.set_message_model(MessageModel::Broadcasting).await;
2345
2346        assert_eq!(consumer.message_model().await, MessageModel::Broadcasting);
2347        assert_eq!(impl_.message_model_config(), MessageModel::Broadcasting);
2348        assert_eq!(
2349            MQConsumerInner::message_model(impl_.as_ref()),
2350            MessageModel::Broadcasting
2351        );
2352
2353        assert_eq!(
2354            consumer.consume_from_where().await,
2355            ConsumeFromWhere::ConsumeFromLastOffset
2356        );
2357        consumer
2358            .set_consume_from_where(ConsumeFromWhere::ConsumeFromFirstOffset)
2359            .await
2360            .expect("valid Java LitePull consumeFromWhere should be accepted");
2361
2362        assert_eq!(
2363            consumer.consume_from_where().await,
2364            ConsumeFromWhere::ConsumeFromFirstOffset
2365        );
2366        assert_eq!(
2367            impl_.consume_from_where_config(),
2368            ConsumeFromWhere::ConsumeFromFirstOffset
2369        );
2370        assert_eq!(
2371            MQConsumerInner::consume_from_where(impl_.as_ref()),
2372            ConsumeFromWhere::ConsumeFromFirstOffset
2373        );
2374
2375        let error = consumer
2376            .set_consume_from_where(ConsumeFromWhere::ConsumeFromMaxOffset)
2377            .await
2378            .expect_err("Java LitePull rejects legacy consumeFromWhere values");
2379        assert!(error.to_string().contains("Invalid ConsumeFromWhere Value"));
2380        assert_eq!(
2381            consumer.consume_from_where().await,
2382            ConsumeFromWhere::ConsumeFromFirstOffset
2383        );
2384
2385        let timestamp = CheetahString::from_static_str("20250102030405");
2386        consumer.set_consume_timestamp(Some(timestamp.clone())).await;
2387
2388        assert_eq!(consumer.consume_timestamp().await, Some(timestamp.clone()));
2389        assert_eq!(impl_.consume_timestamp(), Some(timestamp));
2390
2391        assert_eq!(consumer.topic_metadata_check_interval_millis().await, 30_000);
2392        assert_eq!(impl_.topic_metadata_check_interval_millis(), 30_000);
2393
2394        consumer.set_topic_metadata_check_interval_millis(12_345).await;
2395
2396        assert_eq!(consumer.topic_metadata_check_interval_millis().await, 12_345);
2397        assert_eq!(impl_.topic_metadata_check_interval_millis(), 12_345);
2398    }
2399
2400    #[tokio::test]
2401    async fn set_poll_timeout_updates_initialized_impl_like_java_facade_reference() {
2402        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2403
2404        assert_eq!(consumer.poll_timeout_millis().await, 5000);
2405        assert_eq!(impl_.poll_timeout_millis(), 5000);
2406
2407        consumer.set_poll_timeout_millis(1234).await;
2408
2409        assert_eq!(consumer.poll_timeout_millis().await, 1234);
2410        assert_eq!(impl_.poll_timeout_millis(), 1234);
2411    }
2412
2413    #[tokio::test]
2414    async fn long_polling_timeout_setters_update_initialized_impl_like_java_facade_reference() {
2415        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2416
2417        assert_eq!(consumer.broker_suspend_max_time_millis().await, 20_000);
2418        assert_eq!(impl_.broker_suspend_max_time_millis(), 20_000);
2419        assert_eq!(consumer.consumer_timeout_millis_when_suspend().await, 30_000);
2420        assert_eq!(impl_.consumer_timeout_millis_when_suspend(), 30_000);
2421        assert_eq!(consumer.consumer_pull_timeout_millis().await, 10_000);
2422        assert_eq!(impl_.consumer_pull_timeout_millis(), 10_000);
2423
2424        consumer.set_broker_suspend_max_time_millis(11_111).await;
2425        consumer.set_consumer_timeout_millis_when_suspend(22_222).await;
2426        consumer.set_consumer_pull_timeout_millis(12_345).await;
2427
2428        assert_eq!(consumer.broker_suspend_max_time_millis().await, 11_111);
2429        assert_eq!(impl_.broker_suspend_max_time_millis(), 11_111);
2430        assert_eq!(consumer.consumer_timeout_millis_when_suspend().await, 22_222);
2431        assert_eq!(impl_.consumer_timeout_millis_when_suspend(), 22_222);
2432        assert_eq!(consumer.consumer_pull_timeout_millis().await, 12_345);
2433        assert_eq!(impl_.consumer_pull_timeout_millis(), 12_345);
2434    }
2435
2436    #[tokio::test]
2437    async fn pull_flow_control_setters_update_initialized_impl_like_java_facade_reference() {
2438        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2439
2440        consumer.set_pull_batch_size(64).await;
2441        consumer.set_pull_thread_nums(12).await;
2442        consumer.set_pull_threshold_for_all(20_000).await;
2443        consumer.set_pull_threshold_for_queue(2_000).await;
2444        consumer.set_pull_threshold_size_for_queue(256).await;
2445        consumer.set_consume_max_span(4_000).await;
2446
2447        assert_eq!(consumer.pull_batch_size().await, 64);
2448        assert_eq!(impl_.pull_batch_size(), 64);
2449        assert_eq!(consumer.pull_thread_nums().await, 12);
2450        assert_eq!(impl_.pull_thread_nums(), 12);
2451        assert_eq!(consumer.pull_threshold_for_all().await, 20_000);
2452        assert_eq!(impl_.pull_threshold_for_all(), 20_000);
2453        assert_eq!(consumer.pull_threshold_for_queue().await, 2_000);
2454        assert_eq!(impl_.pull_threshold_for_queue(), 2_000);
2455        assert_eq!(consumer.pull_threshold_size_for_queue().await, 256);
2456        assert_eq!(impl_.pull_threshold_size_for_queue(), 256);
2457        assert_eq!(consumer.consume_max_span().await, 4_000);
2458        assert_eq!(impl_.consume_max_span(), 4_000);
2459    }
2460
2461    #[tokio::test]
2462    async fn broker_selection_setters_update_facade_config_like_java() {
2463        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2464
2465        assert!(!consumer.is_connect_broker_by_user().await);
2466        assert_eq!(
2467            consumer.default_broker_id().await,
2468            rocketmq_common::common::mix_all::MASTER_ID
2469        );
2470        assert!(!impl_.is_connect_broker_by_user());
2471        assert_eq!(impl_.default_broker_id(), rocketmq_common::common::mix_all::MASTER_ID);
2472
2473        consumer.set_connect_broker_by_user(true).await;
2474        consumer.set_default_broker_id(3).await;
2475
2476        assert!(consumer.is_connect_broker_by_user().await);
2477        assert_eq!(consumer.default_broker_id().await, 3);
2478        assert!(impl_.is_connect_broker_by_user());
2479        assert_eq!(impl_.default_broker_id(), 3);
2480    }
2481
2482    #[test]
2483    fn lite_pull_facade_namespace_helpers_use_primary_namespace_like_java() {
2484        let (consumer, _) = new_namespaced_consumer_with_impl();
2485        let queue = MessageQueue::from_parts("TopicB", "broker-a", 3);
2486
2487        assert_eq!(consumer.with_namespace("TopicB").as_str(), "ns%TopicB");
2488        assert_eq!(consumer.with_namespace("ns%TopicB").as_str(), "ns%TopicB");
2489        assert_eq!(consumer.without_namespace("ns%TopicB").as_str(), "TopicB");
2490        assert_eq!(consumer.queue_with_namespace(&queue).topic_str(), "ns%TopicB");
2491        assert_eq!(
2492            consumer
2493                .queue_without_namespace(&MessageQueue::from_parts("ns%TopicB", "broker-a", 3))
2494                .topic_str(),
2495            "TopicB"
2496        );
2497    }
2498
2499    #[tokio::test]
2500    async fn trace_config_accessors_match_java_facade_fields() {
2501        let mut consumer = new_unstarted_consumer();
2502
2503        assert!(!consumer.is_enable_msg_trace());
2504        assert!(consumer.customized_trace_topic().is_none());
2505        assert!(consumer.trace_dispatcher().await.is_none());
2506
2507        consumer.set_enable_msg_trace(true);
2508        consumer.set_customized_trace_topic("TraceTopic");
2509
2510        assert!(consumer.is_enable_msg_trace());
2511        assert_eq!(consumer.customized_trace_topic().as_deref(), Some("TraceTopic"));
2512
2513        consumer.clear_customized_trace_topic();
2514
2515        assert!(consumer.customized_trace_topic().is_none());
2516    }
2517
2518    #[tokio::test]
2519    async fn lite_pull_use_tls_facade_updates_shared_impl_config_like_java_client_config() {
2520        let (consumer, impl_) = new_namespaced_consumer_with_impl();
2521
2522        assert!(!consumer.is_use_tls());
2523        assert!(!impl_.client_config.is_use_tls());
2524
2525        consumer.set_use_tls(true).await;
2526
2527        assert!(consumer.is_use_tls());
2528        assert!(consumer.client_config().is_use_tls());
2529        assert!(impl_.client_config.is_use_tls());
2530    }
2531
2532    #[tokio::test]
2533    async fn lite_pull_use_tls_facade_updates_async_trace_dispatcher() {
2534        let dispatcher = Arc::new(AsyncTraceDispatcher::new(
2535            "lite_pull_tls_trace_group",
2536            Type::Consume,
2537            1,
2538            "",
2539            None,
2540        ));
2541        let consumer = DefaultLitePullConsumer::new(
2542            ArcMut::new(ClientConfig::default()),
2543            ArcMut::new(LitePullConsumerConfig {
2544                consumer_group: CheetahString::from_static_str("lite_pull_tls_trace_group"),
2545                ..Default::default()
2546            }),
2547            None,
2548            Some(dispatcher.clone()),
2549            true,
2550            None,
2551        );
2552
2553        assert!(!consumer.is_use_tls());
2554        assert!(!dispatcher.is_use_tls());
2555
2556        consumer.set_use_tls(true).await;
2557
2558        assert!(consumer.is_use_tls());
2559        assert!(dispatcher.is_use_tls());
2560    }
2561
2562    #[tokio::test]
2563    async fn trace_init_uses_configured_batch_num_like_java() {
2564        let mut client_config = ClientConfig::default();
2565        client_config.set_trace_msg_batch_num(6);
2566        let consumer = DefaultLitePullConsumer::new(
2567            ArcMut::new(client_config),
2568            ArcMut::new(LitePullConsumerConfig {
2569                consumer_group: CheetahString::from_static_str("lite_pull_trace_batch_group"),
2570                ..Default::default()
2571            }),
2572            None,
2573            None,
2574            true,
2575            None,
2576        );
2577        let mut impl_ = ArcMut::new(DefaultLitePullConsumerImpl::new(
2578            consumer.client_config.clone(),
2579            consumer.consumer_config.clone(),
2580        ));
2581        let wrapper = impl_.clone();
2582        impl_.set_default_lite_pull_consumer_impl(wrapper);
2583
2584        consumer
2585            .init_trace_dispatcher_internal(&impl_)
2586            .await
2587            .expect("trace init should create dispatcher");
2588        let dispatcher = consumer
2589            .trace_dispatcher
2590            .read()
2591            .await
2592            .as_ref()
2593            .expect("trace dispatcher should be initialized")
2594            .clone();
2595        let async_dispatcher = dispatcher
2596            .as_any()
2597            .downcast_ref::<AsyncTraceDispatcher>()
2598            .expect("default trace dispatcher should be AsyncTraceDispatcher");
2599        assert_eq!(async_dispatcher.batch_num(), 6);
2600    }
2601
2602    #[tokio::test]
2603    async fn trace_init_registers_custom_dispatcher_and_starts_with_client_config() {
2604        let dispatcher = Arc::new(CapturingTraceDispatcher::default());
2605        let mut client_config = ClientConfig::default();
2606        client_config.set_namesrv_addr(CheetahString::from_static_str("127.0.0.1:9876"));
2607        client_config.set_access_channel(AccessChannel::Cloud);
2608
2609        let consumer = DefaultLitePullConsumer::new(
2610            ArcMut::new(client_config),
2611            ArcMut::new(LitePullConsumerConfig {
2612                consumer_group: CheetahString::from_static_str("lite_pull_trace_group"),
2613                ..Default::default()
2614            }),
2615            None,
2616            Some(dispatcher.clone()),
2617            true,
2618            None,
2619        );
2620        let mut impl_ = ArcMut::new(DefaultLitePullConsumerImpl::new(
2621            consumer.client_config.clone(),
2622            consumer.consumer_config.clone(),
2623        ));
2624        let wrapper = impl_.clone();
2625        impl_.set_default_lite_pull_consumer_impl(wrapper);
2626
2627        consumer
2628            .init_trace_dispatcher_internal(&impl_)
2629            .await
2630            .expect("trace init should register hook");
2631        assert_eq!(impl_.consume_message_hook_count(), 1);
2632
2633        consumer.start_trace_dispatcher().await;
2634        assert_eq!(dispatcher.start_count.load(Ordering::SeqCst), 1);
2635        assert_eq!(
2636            dispatcher
2637                .last_name_srv_addr
2638                .lock()
2639                .expect("name server lock")
2640                .as_deref(),
2641            Some("127.0.0.1:9876")
2642        );
2643        assert_eq!(
2644            *dispatcher.last_access_channel.lock().expect("access channel lock"),
2645            Some(AccessChannel::Cloud)
2646        );
2647    }
2648}