Skip to main content

rocketmq_client_rust/consumer/
default_mq_pull_consumer.rs

1// Copyright 2026 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
15#![allow(deprecated)]
16
17use std::sync::Arc;
18use std::time::Duration;
19
20use crate::ClientError;
21use crate::ClientResult;
22use cheetah_string::CheetahString;
23use rocketmq_model::common::message::message_queue::MessageQueue;
24
25use crate::base::client_config::ClientConfig;
26use crate::consumer::consumer_impl::default_mq_pull_consumer_impl::DefaultMQPullConsumerImpl;
27use crate::consumer::default_lite_pull_consumer::DefaultLitePullConsumer;
28use crate::consumer::default_mq_pull_consumer_builder::DefaultMQPullConsumerBuilder;
29use crate::consumer::message_queue_listener::MessageQueueListener;
30use crate::consumer::message_selector::MessageSelector;
31use crate::consumer::pull_result::PullResult;
32use crate::runtime::ClientRuntime;
33
34/// Validated parameters for one Classic Pull request.
35#[derive(Debug, Clone)]
36pub struct PullOptions {
37    message_queue: MessageQueue,
38    selector: MessageSelector,
39    offset: i64,
40    max_messages: i32,
41    max_size_in_bytes: i32,
42    timeout: Duration,
43    broker_suspend_timeout: Duration,
44    block_if_not_found: bool,
45}
46
47impl PullOptions {
48    /// Creates validated pull options with Java-compatible ordinary-pull defaults.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error when the queue has no topic or broker, the offset is negative, or the
53    /// requested message count is not positive.
54    pub fn new(
55        message_queue: MessageQueue,
56        selector: MessageSelector,
57        offset: i64,
58        max_messages: i32,
59    ) -> ClientResult<Self> {
60        let options = Self {
61            message_queue,
62            selector,
63            offset,
64            max_messages,
65            max_size_in_bytes: i32::MAX,
66            timeout: Duration::from_secs(10),
67            broker_suspend_timeout: Duration::from_secs(20),
68            block_if_not_found: false,
69        };
70        options.validate()?;
71        Ok(options)
72    }
73
74    /// Sets the maximum response payload size accepted from the broker.
75    pub fn max_size_in_bytes(mut self, max_size_in_bytes: i32) -> Self {
76        self.max_size_in_bytes = max_size_in_bytes;
77        self
78    }
79
80    /// Sets the client-side request timeout.
81    pub fn timeout(mut self, timeout: Duration) -> Self {
82        self.timeout = timeout;
83        self
84    }
85
86    /// Sets the broker-side long-poll suspension timeout.
87    pub fn broker_suspend_timeout(mut self, timeout: Duration) -> Self {
88        self.broker_suspend_timeout = timeout;
89        self
90    }
91
92    /// Enables or disables block-if-not-found long polling.
93    pub fn block_if_not_found(mut self, block_if_not_found: bool) -> Self {
94        self.block_if_not_found = block_if_not_found;
95        self
96    }
97
98    /// Validates the complete option set.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error for an incomplete queue, negative offset, non-positive count or size,
103    /// zero timeout, or a block-if-not-found timeout that cannot outlive broker suspension.
104    pub fn validate(&self) -> ClientResult<()> {
105        if self.message_queue.topic().is_empty() {
106            return Err(crate::mq_client_err!("message queue topic is empty"));
107        }
108        if self.message_queue.broker_name().is_empty() {
109            return Err(crate::mq_client_err!("message queue broker name is empty"));
110        }
111        if self.offset < 0 {
112            return Err(crate::mq_client_err!("offset < 0"));
113        }
114        if self.max_messages <= 0 {
115            return Err(crate::mq_client_err!("maxNums <= 0"));
116        }
117        if self.max_size_in_bytes <= 0 {
118            return Err(crate::mq_client_err!("maxSizeInBytes <= 0"));
119        }
120        if self.timeout.is_zero() {
121            return Err(crate::mq_client_err!("pull timeout must be positive"));
122        }
123        if self.broker_suspend_timeout.is_zero() {
124            return Err(crate::mq_client_err!("broker suspend timeout must be positive"));
125        }
126        if self.block_if_not_found && self.timeout <= self.broker_suspend_timeout {
127            return Err(crate::mq_client_err!(
128                "block-if-not-found timeout must exceed broker suspend timeout"
129            ));
130        }
131        Ok(())
132    }
133
134    /// Returns the target message queue.
135    pub fn message_queue(&self) -> &MessageQueue {
136        &self.message_queue
137    }
138
139    /// Returns the server-side selector.
140    pub fn selector(&self) -> &MessageSelector {
141        &self.selector
142    }
143
144    /// Returns the requested queue offset.
145    pub fn offset(&self) -> i64 {
146        self.offset
147    }
148
149    /// Returns the maximum number of messages requested.
150    pub fn max_messages(&self) -> i32 {
151        self.max_messages
152    }
153
154    /// Returns the maximum response payload size.
155    pub fn max_size_in_bytes_value(&self) -> i32 {
156        self.max_size_in_bytes
157    }
158
159    /// Returns the client-side timeout.
160    pub fn timeout_value(&self) -> Duration {
161        self.timeout
162    }
163
164    /// Returns the broker-side suspension timeout.
165    pub fn broker_suspend_timeout_value(&self) -> Duration {
166        self.broker_suspend_timeout
167    }
168
169    /// Returns whether block-if-not-found is enabled.
170    pub fn is_block_if_not_found(&self) -> bool {
171        self.block_if_not_found
172    }
173
174    pub(crate) fn with_message_queue(mut self, message_queue: MessageQueue) -> Self {
175        self.message_queue = message_queue;
176        self
177    }
178}
179
180/// Callback for an asynchronous Classic Pull request.
181pub trait ClassicPullCallback: Send + Sync + 'static {
182    /// Receives one fully decoded and filtered pull result.
183    fn on_success(&self, pull_result: PullResult);
184
185    /// Receives a transport, broker, timeout, or validation error.
186    fn on_exception(&self, error: ClientError);
187}
188
189/// Function callback accepted by [`DefaultMQPullConsumer::pull_async_with_options`].
190impl<F> ClassicPullCallback for F
191where
192    F: Fn(Result<PullResult, ClientError>) + Send + Sync + 'static,
193{
194    fn on_success(&self, pull_result: PullResult) {
195        self(Ok(pull_result));
196    }
197
198    fn on_exception(&self, error: ClientError) {
199        self(Err(error));
200    }
201}
202
203/// Java Classic Pull compatibility facade backed by the shared Rust client runtime.
204#[deprecated(
205    since = "0.9.0",
206    note = "Classic Pull is retained for compatibility; prefer DefaultLitePullConsumer for new applications"
207)]
208#[derive(Clone, Default)]
209pub struct DefaultMQPullConsumer {
210    consumer_group: Option<CheetahString>,
211    implementation: Option<DefaultMQPullConsumerImpl>,
212}
213
214#[allow(deprecated)]
215impl DefaultMQPullConsumer {
216    /// Creates a detached compatibility value.
217    ///
218    /// Use [`Self::builder`] to obtain a runnable consumer. The detached constructor is retained
219    /// for source compatibility and fails closed on runtime operations.
220    pub fn new() -> Self {
221        Self::default()
222    }
223
224    /// Creates a detached compatibility value with a group name.
225    pub fn with_consumer_group(consumer_group: impl Into<CheetahString>) -> Self {
226        Self {
227            consumer_group: Some(consumer_group.into()),
228            implementation: None,
229        }
230    }
231
232    /// Returns a builder that requires an application-owned runtime.
233    pub fn builder(client_runtime: Arc<ClientRuntime>) -> DefaultMQPullConsumerBuilder {
234        DefaultMQPullConsumerBuilder::new(client_runtime)
235    }
236
237    pub(crate) fn from_lite_consumer(
238        consumer_group: CheetahString,
239        lite_consumer: DefaultLitePullConsumer,
240        consumer_pull_timeout: Duration,
241        broker_suspend_timeout: Duration,
242        consumer_timeout_when_suspend: Duration,
243    ) -> ClientResult<Self> {
244        Ok(Self {
245            consumer_group: Some(consumer_group),
246            implementation: Some(DefaultMQPullConsumerImpl::from_lite_consumer(
247                lite_consumer,
248                consumer_pull_timeout,
249                broker_suspend_timeout,
250                consumer_timeout_when_suspend,
251            )?),
252        })
253    }
254
255    fn implementation(&self) -> ClientResult<&DefaultMQPullConsumerImpl> {
256        self.implementation.as_ref().ok_or_else(|| {
257            ClientError::not_initialized(
258                "DefaultMQPullConsumer has no ClientRuntime; create it with DefaultMQPullConsumer::builder",
259            )
260        })
261    }
262
263    /// Returns the configured consumer group, if present.
264    pub fn consumer_group(&self) -> Option<&CheetahString> {
265        self.consumer_group.as_ref()
266    }
267
268    /// Returns the client configuration used by this consumer.
269    ///
270    /// # Errors
271    ///
272    /// Returns an initialization error for a detached compatibility value.
273    pub fn client_config(&self) -> ClientResult<Arc<ClientConfig>> {
274        self.implementation()?.client_config()
275    }
276
277    /// Starts the consumer.
278    ///
279    /// # Errors
280    ///
281    /// Returns an initialization error for a detached value, a stable lifecycle error when the
282    /// consumer was already started or shut down, or the underlying client startup error.
283    pub async fn start(&self) -> ClientResult<()> {
284        self.implementation()?.start().await
285    }
286
287    /// Shuts the consumer down and awaits its runtime-owned client and rebalance tasks.
288    ///
289    /// # Errors
290    ///
291    /// Returns an initialization error for a detached value or an underlying client shutdown
292    /// error. Repeated shutdown is idempotent.
293    pub async fn shutdown(&self) -> ClientResult<()> {
294        self.implementation()?.shutdown().await
295    }
296
297    /// Returns whether the consumer is running.
298    pub async fn is_running(&self) -> bool {
299        match &self.implementation {
300            Some(implementation) => implementation.is_running().await,
301            None => false,
302        }
303    }
304
305    /// Pulls by tag expression using the default ordinary-pull timeout.
306    ///
307    /// # Errors
308    ///
309    /// Returns an error when the consumer is not running, the arguments are invalid, route or
310    /// transport resolution fails, the broker rejects the request, or the request times out.
311    pub async fn pull(
312        &self,
313        message_queue: &MessageQueue,
314        sub_expression: &str,
315        offset: i64,
316        max_messages: i32,
317    ) -> ClientResult<PullResult> {
318        let implementation = self.implementation()?;
319        let options = PullOptions::new(
320            message_queue.clone(),
321            MessageSelector::by_tag(sub_expression),
322            offset,
323            max_messages,
324        )?
325        .timeout(implementation.consumer_pull_timeout())
326        .broker_suspend_timeout(implementation.broker_suspend_timeout());
327        implementation.pull_with_options(options).await
328    }
329
330    /// Pulls with an explicit TAG or SQL selector.
331    ///
332    /// # Errors
333    ///
334    /// Returns the same validation, lifecycle, transport, broker, and timeout errors as
335    /// [`Self::pull`].
336    pub async fn pull_with_selector(
337        &self,
338        message_queue: &MessageQueue,
339        selector: MessageSelector,
340        offset: i64,
341        max_messages: i32,
342    ) -> ClientResult<PullResult> {
343        let implementation = self.implementation()?;
344        implementation
345            .pull_with_options(
346                PullOptions::new(message_queue.clone(), selector, offset, max_messages)?
347                    .timeout(implementation.consumer_pull_timeout())
348                    .broker_suspend_timeout(implementation.broker_suspend_timeout()),
349            )
350            .await
351    }
352
353    /// Starts an asynchronous tag pull using the Java-compatible narrow signature.
354    ///
355    /// # Errors
356    ///
357    /// Returns an error before scheduling when validation, lifecycle, route resolution, or the
358    /// initial transport request fails. Later failures are delivered to the callback.
359    pub async fn pull_async<C>(
360        &self,
361        message_queue: &MessageQueue,
362        sub_expression: &str,
363        offset: i64,
364        max_messages: i32,
365        callback: C,
366    ) -> ClientResult<()>
367    where
368        C: ClassicPullCallback,
369    {
370        let implementation = self.implementation()?;
371        let options = PullOptions::new(
372            message_queue.clone(),
373            MessageSelector::by_tag(sub_expression),
374            offset,
375            max_messages,
376        )?
377        .timeout(implementation.consumer_pull_timeout())
378        .broker_suspend_timeout(implementation.broker_suspend_timeout());
379        implementation.pull_async_with_options(options, callback).await
380    }
381
382    /// Starts an asynchronous pull with an explicit TAG or SQL selector.
383    ///
384    /// # Errors
385    ///
386    /// Returns an error before scheduling when validation, lifecycle, route resolution, or the
387    /// initial transport request fails. Later failures are delivered to the callback.
388    pub async fn pull_async_with_selector<C>(
389        &self,
390        message_queue: &MessageQueue,
391        selector: MessageSelector,
392        offset: i64,
393        max_messages: i32,
394        callback: C,
395    ) -> ClientResult<()>
396    where
397        C: ClassicPullCallback,
398    {
399        let implementation = self.implementation()?;
400        let options = PullOptions::new(message_queue.clone(), selector, offset, max_messages)?
401            .timeout(implementation.consumer_pull_timeout())
402            .broker_suspend_timeout(implementation.broker_suspend_timeout());
403        implementation.pull_async_with_options(options, callback).await
404    }
405
406    /// Pulls using a fully typed option set.
407    ///
408    /// # Errors
409    ///
410    /// Returns a validation, lifecycle, route, transport, broker, or timeout error.
411    pub async fn pull_with_options(&self, options: PullOptions) -> ClientResult<PullResult> {
412        self.implementation()?.pull_with_options(options).await
413    }
414
415    /// Pulls with broker suspension when no message is immediately available.
416    ///
417    /// # Errors
418    ///
419    /// Returns a validation, lifecycle, route, transport, broker, or timeout error.
420    pub async fn pull_block_if_not_found(
421        &self,
422        message_queue: &MessageQueue,
423        sub_expression: &str,
424        offset: i64,
425        max_messages: i32,
426    ) -> ClientResult<PullResult> {
427        let implementation = self.implementation()?;
428        let options = PullOptions::new(
429            message_queue.clone(),
430            MessageSelector::by_tag(sub_expression),
431            offset,
432            max_messages,
433        )?
434        .timeout(implementation.consumer_timeout_when_suspend())
435        .broker_suspend_timeout(implementation.broker_suspend_timeout())
436        .block_if_not_found(true);
437        implementation.pull_with_options(options).await
438    }
439
440    /// Starts an asynchronous pull whose completion runs on the client's callback executor.
441    ///
442    /// # Errors
443    ///
444    /// Returns an error before scheduling when validation, lifecycle, route resolution, or the
445    /// initial transport request fails. Later failures are delivered to the callback.
446    pub async fn pull_async_with_options<C>(&self, options: PullOptions, callback: C) -> ClientResult<()>
447    where
448        C: ClassicPullCallback,
449    {
450        self.implementation()?.pull_async_with_options(options, callback).await
451    }
452
453    /// Starts an asynchronous block-if-not-found pull.
454    ///
455    /// # Errors
456    ///
457    /// Returns an error before scheduling when validation, lifecycle, route resolution, or the
458    /// initial transport request fails. Later failures are delivered to the callback.
459    pub async fn pull_block_if_not_found_async<C>(
460        &self,
461        message_queue: &MessageQueue,
462        sub_expression: &str,
463        offset: i64,
464        max_messages: i32,
465        callback: C,
466    ) -> ClientResult<()>
467    where
468        C: ClassicPullCallback,
469    {
470        let implementation = self.implementation()?;
471        let options = PullOptions::new(
472            message_queue.clone(),
473            MessageSelector::by_tag(sub_expression),
474            offset,
475            max_messages,
476        )?
477        .timeout(implementation.consumer_timeout_when_suspend())
478        .broker_suspend_timeout(implementation.broker_suspend_timeout())
479        .block_if_not_found(true);
480        implementation.pull_async_with_options(options, callback).await
481    }
482
483    /// Fetches all readable queues for a topic.
484    ///
485    /// # Errors
486    ///
487    /// Returns an error when the consumer is not running or topic route lookup fails.
488    pub async fn fetch_subscribe_message_queues(&self, topic: &str) -> ClientResult<Vec<MessageQueue>> {
489        self.implementation()?.fetch_subscribe_message_queues(topic).await
490    }
491
492    /// Registers a listener for stable topic queue-set changes.
493    ///
494    /// # Errors
495    ///
496    /// Returns an error for a blank topic, a detached consumer, or a failed running-consumer
497    /// subscription update.
498    pub async fn register_message_queue_listener<L>(&self, topic: &str, listener: L) -> ClientResult<()>
499    where
500        L: MessageQueueListener + 'static,
501    {
502        self.implementation()?
503            .register_message_queue_listener(topic, Arc::new(listener))
504            .await
505    }
506
507    /// Updates the local consume offset for a queue.
508    ///
509    /// # Errors
510    ///
511    /// Returns an error when the consumer is not running, the offset is negative, or the offset
512    /// store is unavailable.
513    pub async fn update_consume_offset(&self, message_queue: &MessageQueue, offset: i64) -> ClientResult<()> {
514        self.implementation()?
515            .update_consume_offset(message_queue, offset)
516            .await
517    }
518
519    /// Fetches a queue offset from memory or the configured offset store.
520    ///
521    /// # Errors
522    ///
523    /// Returns an error when the consumer is not running, the offset store is unavailable, or a
524    /// broker-backed read fails.
525    pub async fn fetch_consume_offset(&self, message_queue: &MessageQueue, from_store: bool) -> ClientResult<i64> {
526        self.implementation()?
527            .fetch_consume_offset(message_queue, from_store)
528            .await
529    }
530
531    /// Searches the queue offset nearest to a store timestamp.
532    ///
533    /// # Errors
534    ///
535    /// Returns an error when the consumer is not running or the broker query fails.
536    pub async fn search_offset(&self, message_queue: &MessageQueue, timestamp: u64) -> ClientResult<i64> {
537        self.implementation()?.search_offset(message_queue, timestamp).await
538    }
539
540    /// Returns the maximum queue offset.
541    ///
542    /// # Errors
543    ///
544    /// Returns an error when the consumer is not running or the broker query fails.
545    pub async fn max_offset(&self, message_queue: &MessageQueue) -> ClientResult<i64> {
546        self.implementation()?.max_offset(message_queue).await
547    }
548
549    /// Returns the minimum queue offset.
550    ///
551    /// # Errors
552    ///
553    /// Returns an error when the consumer is not running or the broker query fails.
554    pub async fn min_offset(&self, message_queue: &MessageQueue) -> ClientResult<i64> {
555        self.implementation()?.min_offset(message_queue).await
556    }
557
558    /// Returns the functional implementation handle.
559    ///
560    /// # Errors
561    ///
562    /// Returns an initialization error for a detached compatibility value.
563    pub fn default_mq_pull_consumer_impl(&self) -> ClientResult<DefaultMQPullConsumerImpl> {
564        self.implementation().cloned()
565    }
566}
567
568/// Common async surface implemented by Classic Pull facades.
569#[allow(async_fn_in_trait)]
570#[deprecated(
571    since = "0.9.0",
572    note = "Classic Pull is retained for compatibility; prefer DefaultLitePullConsumer for new applications"
573)]
574pub trait MQPullConsumer: Send + Sync {
575    /// Starts the consumer.
576    async fn start(&self) -> ClientResult<()>;
577
578    /// Shuts the consumer down.
579    async fn shutdown(&self) -> ClientResult<()>;
580
581    /// Pulls one queue by tag expression.
582    async fn pull(
583        &self,
584        message_queue: &MessageQueue,
585        sub_expression: &str,
586        offset: i64,
587        max_messages: i32,
588    ) -> ClientResult<PullResult>;
589}
590
591#[allow(deprecated)]
592impl MQPullConsumer for DefaultMQPullConsumer {
593    async fn start(&self) -> ClientResult<()> {
594        DefaultMQPullConsumer::start(self).await
595    }
596
597    async fn shutdown(&self) -> ClientResult<()> {
598        DefaultMQPullConsumer::shutdown(self).await
599    }
600
601    async fn pull(
602        &self,
603        message_queue: &MessageQueue,
604        sub_expression: &str,
605        offset: i64,
606        max_messages: i32,
607    ) -> ClientResult<PullResult> {
608        DefaultMQPullConsumer::pull(self, message_queue, sub_expression, offset, max_messages).await
609    }
610}