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