Skip to content

[ISSUE #6526]♻️Refactor consume message handling for improved clarity and performance#6527

Merged
rocketmq-rust-bot merged 2 commits intomainfrom
feat-6526
Feb 27, 2026
Merged

[ISSUE #6526]♻️Refactor consume message handling for improved clarity and performance#6527
rocketmq-rust-bot merged 2 commits intomainfrom
feat-6526

Conversation

@mxsm
Copy link
Owner

@mxsm mxsm commented Feb 26, 2026

Which Issue(s) This PR Fixes(Closes)

Brief Description

How Did You Test This Change?

Summary by CodeRabbit

  • Refactor
    • Message consumption flow reworked for simpler, more efficient handling and clearer lifecycle management.
  • Bug Fixes
    • Improved error mapping and join/panic handling for background consume tasks to reduce silent failures.
    • Fixed acknowledgement and delay propagation to ensure more reliable message ack/delivery behavior.
  • Reliability
    • Consume operations now run in isolated blocking tasks to improve stability under load.

@rocketmq-rust-bot
Copy link
Collaborator

🔊@mxsm 🚀Thanks for your contribution🎉!

💡CodeRabbit(AI) will review your code first🔥!

Note

🚨The code review suggestions from CodeRabbit are to be used as a reference only, and the PR submitter can decide whether to make changes based on their own judgment. Ultimately, the project management personnel will conduct the final code review💥.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Walkthrough

The PR refactors ConsumeRequest in the POP concurrent consumer: it now accepts Vec and explicit consumer dependencies (consumer_group, message_listener, default_mqpush_consumer_impl), and run() consumes the request by value. Message handling uses blocking tasks and direct MessageExt vectors instead of MessageClientExt wrappers.

Changes

Cohort / File(s) Summary
ConsumeRequest & POP consume flow
rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs
Reworked ConsumeRequest::new to accept Vec<MessageExt> and new fields (consumer_group, message_listener, default_mqpush_consumer_impl). Changed ConsumeRequest::run to take ownership (mut self) and moved message consumption into spawn_blocking with updated error/join handling, context propagation, and ack/delay logic adjustments. Removed MessageClientExt-to-MessageExt conversion and updated callers (consume_message_directly, submit_pop_consume_request) to match new signature.

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as Scheduler
    participant Service as ConsumeMessagePopConcurrentlyService
    participant Req as ConsumeRequest
    participant Blocking as BlockingThread
    participant Listener as MessageListenerConcurrently
    participant ConsumerImpl as DefaultMQPushConsumerImpl

    Scheduler->>Service: submit_pop_consume_request(msgs, mq, pq, ...)
    Service->>Req: new(msgs: Vec<MessageExt>, consumer_group, message_listener, default_mqpush_consumer_impl)
    Service->>Req: run(mut self)
    Req->>Blocking: spawn_blocking(consume_message(..., listener, consumer_impl))
    Blocking->>Listener: invoke.consume_message(msgs, context)
    Listener-->>Blocking: ConsumeConcurrentlyStatus
    Blocking->>Req: return result or error
    Req->>Service: propagate result, ack/delay handling, update process_queue
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • TeslaRustor
  • SpaceXCN
  • rocketmq-rust-bot

Poem

🐰 I nibbled code and found a seam,
Messages unwrapped, now clean and lean,
Spawned a thread for the heavy feat,
Ownership hops — the flow is neat 🥕
Cheers from the burrow, logs all green.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The linked issue #6526 provides only a generic refactoring objective with no specific coding requirements or acceptance criteria to validate against. The linked issue lacks detailed requirements. Verify that the refactoring achieves the intended clarity and performance improvements through code review.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary refactoring objective (consume message handling) and mentions the improvements (clarity and performance) that align with the changeset.
Out of Scope Changes check ✅ Passed All changes focus on refactoring consume message handling in ConsumeRequest; no unrelated modifications detected beyond the stated refactoring scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-6526

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs (3)

435-435: Consider documenting the no-op success callback.

The empty on_success implementation is likely intentional since no action is needed on successful invisible time change. However, adding a comment would improve code clarity.

📝 Suggested documentation
-            fn on_success(&self, ack_result: AckResult) {}
+            fn on_success(&self, ack_result: AckResult) {
+                // No action needed on successful invisible time change
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`
at line 435, The empty on_success(&self, ack_result: AckResult) {}
implementation is intentionally a no-op; update the implementation in
consume_message_pop_concurrently_service by adding a concise comment inside the
function (or above the method) stating that success requires no further action
(e.g., "No-op: no action required on successful invisible time change"), and
include the ack_result parameter name in the comment if helpful to clarify it's
intentionally unused.

90-90: Verify the empty start method is intentional.

The start method implementation is empty. If this is intentional because this service doesn't require initialization, consider adding a comment to clarify. Otherwise, ensure no initialization logic is missing.

📝 Suggested documentation
-    fn start(&mut self, this: ArcMut<Self>) {}
+    fn start(&mut self, this: ArcMut<Self>) {
+        // No initialization required for pop-based concurrent consumption
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`
at line 90, The empty start method on ConsumeMessagePopConcurrentlyService (fn
start(&mut self, this: ArcMut<Self>) {}) appears intentional or missing work;
either add a clear comment inside the method (e.g., "// no initialization
required for pop concurrent service") to document that no startup logic is
needed, or implement the required initialization logic there (initialize fields,
spawn worker/task, register timers, or call existing init helpers) so the
service is correctly started; edit the fn start(...) in
consume_message_pop_concurrently_service.rs accordingly and ensure any
referenced helpers or fields used during startup are invoked (referencing the
ConsumeMessagePopConcurrentlyService type and start method to locate the
change).

599-644: Consider the performance impact of message cloning.

Line 601 clones all messages before passing them to spawn_blocking. While this is necessary for ownership transfer into the blocking task (since self.msgs is used later at line 702), the cloning could have performance implications for large message batches.

Consider whether restructuring the code to move message ownership into the blocking task and return the messages along with the result would be feasible. However, the current implementation provides good error recovery and may be an acceptable trade-off.

Alternative approach to consider:

// Move msgs into blocking task and return them
let msgs_owned = std::mem::take(&mut self.msgs);
let blocking_result = tokio::task::spawn_blocking(move || {
    let msgs_refs: Vec<&MessageExt> = msgs_owned.iter().map(|m| m.as_ref()).collect();
    let result = listener.consume_message(&msgs_refs, &context);
    (result, context, msgs_owned)
})
.await;
// Then restore self.msgs from the returned tuple

However, the current implementation may be clearer and the cloning cost might be acceptable depending on message sizes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`
around lines 599 - 644, Cloning all messages into msgs_cloned before
spawn_blocking (self.msgs, MessageExt) hurts performance for large batches;
instead move ownership of the messages into the blocking task and return them
with the result so you can restore self.msgs after await. Use
std::mem::take(&mut self.msgs) (or similar) to obtain msgs_owned, pass
msgs_owned and listener/context into tokio::task::spawn_blocking, have the
closure call listener.consume_message with references to msgs_owned and return
(result, context, msgs_owned), await the task, set status/has_exception from the
returned result and then assign the returned msgs_owned back to self.msgs so you
avoid cloning while keeping the existing error handling around spawn_blocking,
ConsumeConcurrentlyContext, and logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`:
- Line 435: The empty on_success(&self, ack_result: AckResult) {} implementation
is intentionally a no-op; update the implementation in
consume_message_pop_concurrently_service by adding a concise comment inside the
function (or above the method) stating that success requires no further action
(e.g., "No-op: no action required on successful invisible time change"), and
include the ack_result parameter name in the comment if helpful to clarify it's
intentionally unused.
- Line 90: The empty start method on ConsumeMessagePopConcurrentlyService (fn
start(&mut self, this: ArcMut<Self>) {}) appears intentional or missing work;
either add a clear comment inside the method (e.g., "// no initialization
required for pop concurrent service") to document that no startup logic is
needed, or implement the required initialization logic there (initialize fields,
spawn worker/task, register timers, or call existing init helpers) so the
service is correctly started; edit the fn start(...) in
consume_message_pop_concurrently_service.rs accordingly and ensure any
referenced helpers or fields used during startup are invoked (referencing the
ConsumeMessagePopConcurrentlyService type and start method to locate the
change).
- Around line 599-644: Cloning all messages into msgs_cloned before
spawn_blocking (self.msgs, MessageExt) hurts performance for large batches;
instead move ownership of the messages into the blocking task and return them
with the result so you can restore self.msgs after await. Use
std::mem::take(&mut self.msgs) (or similar) to obtain msgs_owned, pass
msgs_owned and listener/context into tokio::task::spawn_blocking, have the
closure call listener.consume_message with references to msgs_owned and return
(result, context, msgs_owned), await the task, set status/has_exception from the
returned result and then assign the returned msgs_owned back to self.msgs so you
avoid cloning while keeping the existing error handling around spawn_blocking,
ConsumeConcurrentlyContext, and logging.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63f18c2 and c1cfc44.

📒 Files selected for processing (1)
  • rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs (1)

180-183: Avoid deep-cloning MessageExt in the consume hot path.

Line 180 and Line 601 clone full messages before listener invocation. This adds avoidable allocation/copy overhead per batch and can hurt throughput under load.

Refactor sketch (use ArcMut clones instead of MessageExt deep clones)
- let msgs_cloned: Vec<MessageExt> = self.msgs.iter().map(|m| m.as_ref().clone()).collect();
+ let msgs_cloned = self.msgs.clone();

  let blocking_result = tokio::task::spawn_blocking(move || {
-     let msgs_refs: Vec<&MessageExt> = msgs_cloned.iter().collect();
+     let msgs_refs: Vec<&MessageExt> = msgs_cloned.iter().map(|m| m.as_ref()).collect();
      let result = listener.consume_message(&msgs_refs, &context);
      (result, context)
  })

Also applies to: 601-609

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`
around lines 180 - 183, The current hot path deeply clones MessageExt into
msgs_cloned and then creates msgs_refs to call listener.consume_message, which
causes heavy per-batch allocation; instead stop deep-copying MessageExt and use
cheap Arc clones: change the message ownership to Arc<MessageExt> upstream (or
wrap each incoming MessageExt into an Arc once), then here build a
Vec<Arc<MessageExt>> by cloning the Arc (cheap) and pass either a slice of
Arc<MessageExt> to listener.consume_message or a slice of &MessageExt obtained
by Arc::as_ref(); update listener.consume_message signature accordingly and
remove the MessageExt deep clones around msgs_cloned and msgs_refs and the heavy
allocations inside the tokio::task::spawn_blocking call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`:
- Around line 482-483: The ConsumeRequest struct currently stores
default_mqpush_consumer_impl as Option<ArcMut<DefaultMQPushConsumerImpl>> but
its usage calls .as_ref().unwrap() (e.g., in ConsumeRequest
construction/handlers), which can panic; fix by either (A) making the field
non-optional everywhere (change the type to ArcMut<DefaultMQPushConsumerImpl> on
ConsumeRequest and update all callsites that pass
self.default_mqpush_consumer_impl.clone() to provide the ArcMut directly), or
(B) handle the None case explicitly (replace .as_ref().unwrap() with a safe
match/if let that returns an error/early return or logs and aborts the consume
operation when default_mqpush_consumer_impl is None). Apply the same change to
other unwrap sites that reference default_mqpush_consumer_impl to eliminate
unconditional unwraps.

---

Nitpick comments:
In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`:
- Around line 180-183: The current hot path deeply clones MessageExt into
msgs_cloned and then creates msgs_refs to call listener.consume_message, which
causes heavy per-batch allocation; instead stop deep-copying MessageExt and use
cheap Arc clones: change the message ownership to Arc<MessageExt> upstream (or
wrap each incoming MessageExt into an Arc once), then here build a
Vec<Arc<MessageExt>> by cloning the Arc (cheap) and pass either a slice of
Arc<MessageExt> to listener.consume_message or a slice of &MessageExt obtained
by Arc::as_ref(); update listener.consume_message signature accordingly and
remove the MessageExt deep clones around msgs_cloned and msgs_refs and the heavy
allocations inside the tokio::task::spawn_blocking call.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1cfc44 and 4dcf276.

📒 Files selected for processing (1)
  • rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs

Comment on lines +482 to 483
default_mqpush_consumer_impl: Option<ArcMut<DefaultMQPushConsumerImpl>>,
) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect all ConsumeRequest::new callsites ==="
rg -nP --type=rust '\bConsumeRequest::new\s*\(' -C3

echo
echo "=== Check for explicit None passed to ConsumeRequest::new ==="
rg -nP --type=rust -U 'ConsumeRequest::new\s*\((?s).*?\bNone\b' -C2

echo
echo "=== Inspect service constructor callsites ==="
rg -nP --type=rust '\bConsumeMessagePopConcurrentlyService::new\s*\(' -C3

echo
echo "=== Check for explicit None passed to service constructor ==="
rg -nP --type=rust -U 'ConsumeMessagePopConcurrentlyService::new\s*\((?s).*?\bNone\b' -C2

Repository: mxsm/rocketmq-rust

Length of output: 50374


🏁 Script executed:

#!/bin/bash
# Read the specific file and extract relevant sections
echo "=== File structure around lines 482-483 and 509-510 ==="
sed -n '475,520p' rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs | cat -n

echo
echo "=== Check for unwrap() usage on default_mqpush_consumer_impl ==="
rg -n 'default_mqpush_consumer_impl.*unwrap|unwrap.*default_mqpush_consumer_impl' rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs -C2

echo
echo "=== Check run() method implementation around line 564 ==="
sed -n '550,580p' rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs | cat -n

Repository: mxsm/rocketmq-rust

Length of output: 3956


ConsumeRequest has an optional field that is always unwrapped without None handling.

The default_mqpush_consumer_impl parameter at line 482 accepts Option<ArcMut<DefaultMQPushConsumerImpl>>, but line 564 unwraps it unconditionally with .as_ref().unwrap(). All callsites (lines 240, 275) pass self.default_mqpush_consumer_impl.clone() from the service, suggesting this field should never be None. Either make the field non-optional or explicitly handle the None case instead of panicking.

Suggested direction
-    default_mqpush_consumer_impl: Option<ArcMut<DefaultMQPushConsumerImpl>>,
+    default_mqpush_consumer_impl: ArcMut<DefaultMQPushConsumerImpl>,
-        default_mqpush_consumer_impl: Option<ArcMut<DefaultMQPushConsumerImpl>>,
+        default_mqpush_consumer_impl: ArcMut<DefaultMQPushConsumerImpl>,

Also applies to: 509-510

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rocketmq-client/src/consumer/consumer_impl/consume_message_pop_concurrently_service.rs`
around lines 482 - 483, The ConsumeRequest struct currently stores
default_mqpush_consumer_impl as Option<ArcMut<DefaultMQPushConsumerImpl>> but
its usage calls .as_ref().unwrap() (e.g., in ConsumeRequest
construction/handlers), which can panic; fix by either (A) making the field
non-optional everywhere (change the type to ArcMut<DefaultMQPushConsumerImpl> on
ConsumeRequest and update all callsites that pass
self.default_mqpush_consumer_impl.clone() to provide the ArcMut directly), or
(B) handle the None case explicitly (replace .as_ref().unwrap() with a safe
match/if let that returns an error/early return or logs and aborts the consume
operation when default_mqpush_consumer_impl is None). Apply the same change to
other unwrap sites that reference default_mqpush_consumer_impl to eliminate
unconditional unwraps.

@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 0% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.09%. Comparing base (e2d20c0) to head (4dcf276).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...r_impl/consume_message_pop_concurrently_service.rs 0.00% 74 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6527      +/-   ##
==========================================
- Coverage   42.09%   42.09%   -0.01%     
==========================================
  Files         947      947              
  Lines      132252   132264      +12     
==========================================
+ Hits        55667    55672       +5     
- Misses      76585    76592       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rocketmq-rust-bot rocketmq-rust-bot merged commit fc1ee91 into main Feb 27, 2026
10 of 13 checks passed
@rocketmq-rust-bot rocketmq-rust-bot added approved PR has approved and removed ready to review waiting-review waiting review this PR labels Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI review first Ai review pr first approved PR has approved auto merge refactor♻️ refactor code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor♻️] Refactor consume message handling for improved clarity and performance

3 participants