Skip to content

[ISSUE #380] Fix NOT_CONSUME_YET false positive in message track detail#424

Open
messere1 wants to merge 3 commits into
apache:masterfrom
messere1:contest/bugfix-pr07-not-consume-yet
Open

[ISSUE #380] Fix NOT_CONSUME_YET false positive in message track detail#424
messere1 wants to merge 3 commits into
apache:masterfrom
messere1:contest/bugfix-pr07-not-consume-yet

Conversation

@messere1

@messere1 messere1 commented Jul 8, 2026

Copy link
Copy Markdown

What is the purpose of the change

Fix #380: Messages that have already been consumed are incorrectly displayed as NOT_CONSUME_YET in the message track detail page.

Root Cause

MessageServiceImpl.messageTrackDetail() delegates to DefaultMQAdminExtImpl.consumed() which determines whether a message has been consumed by comparing the consumer offset against the message's queue offset. However, consumed() also requires an exact broker-address match between msg.getStoreHost() and the broker's registered address:

String addr = NetworkUtil.convert2IpString(brokerData.getBrokerAddrs().get(MixAll.MASTER_ID));
if (NetworkUtil.socketAddress2String(msg.getStoreHost()).equals(addr)) {
    if (next.getValue().getConsumerOffset() > msg.getQueueOffset()) {
        return true;
    }
}

When the broker registers with a hostname (e.g. broker-a:10911) and the DNS resolution inside the dashboard JVM differs from the broker's environment, convert2IpString produces a different IP string than socketAddress2String(msg.getStoreHost()), causing the address comparison to silently fail. As a result, consumed() returns false and the message is reported as NOT_CONSUME_YET even though the consumer offset has already advanced past the message.

A community member also confirmed in the issue: "用控制台创建消费者组 消费状态就正常了" — suggesting the address-matching path is sensitive to how the consumer group and broker are configured.

Fix

Initial fix (commit 1)

Add a fallback verification in MessageServiceImpl.messageTrackDetail(): when the underlying admin API returns NOT_CONSUME_YET, re-check the consumer offset directly via examineConsumeStats, matching on topic + queueId + offset and skipping the fragile broker-address comparison. If the consumer offset has advanced past the message's queue offset, the track type is corrected to CONSUMED.

Hardening (commit 2 — multi-broker correctness)

After further analysis, the initial fix had a subtle flaw: matching only on topic + queueId is insufficient in multi-broker clusters where broker-a and broker-b both have a queue with the same queueId but independent offsets. This could cause a cross-broker false positive if the wrong broker's offset happened to be higher.

The hardening introduces two improvements:

  1. Broker-level address resolution (resolveBrokerName()): Instead of blindly matching all queues with the same topic+queueId, the method now resolves which broker actually holds the message by comparing msg.getStoreHost() against topic route data at the IP level. Only the MessageQueue whose brokerName matches the resolved broker is considered for offset comparison. If brokerName cannot be resolved (e.g. route data unavailable), the method conservatively returns false — preserving the original NOT_CONSUME_YET rather than risking a false positive.

  2. Group-level ConsumeStats cache: When multiple NOT_CONSUME_YET tracks belong to the same consumer group, examineConsumeStats is now called only once per group (via computeIfAbsent), eliminating redundant RPC calls.

Single-broker short-circuit (commit 3 — DNS/hostname resilience)

The IP-level matching in resolveBrokerName() can still fail in NAT/container environments where DNS resolution differs between the dashboard JVM and the broker. However, single-broker deployments — the most common scenario for issue #380 (dev/test/small-scale prod) — can be handled with zero ambiguity: if there is only one broker in the route data, that broker holds the message by definition.

The short-circuit checks brokerDatas.size() == 1 before attempting IP matching, and directly returns the sole brokerName. This makes the fix effective even in the most pathological DNS/hostname mismatch scenarios, with no risk of cross-broker confusion.

The fallback remains:

  • Safe: it only upgrades NOT_CONSUME_YETCONSUMED, never the reverse.
  • Resilient: if examineConsumeStats throws or resolveBrokerName fails, the original NOT_CONSUME_YET is preserved.
  • Precise: offset comparison is scoped to the correct broker, preventing cross-broker false positives.
  • Single-broker immune: DNS/hostname mismatches are bypassed when there is only one broker.

Brief changelog

  • MessageServiceImpl.java:
    • messageTrackDetail(): post-process NOT_CONSUME_YET tracks via examineConsumeStats with group-level cache.
    • isConsumedByGroup(MessageExt, ConsumeStats): signature changed to accept pre-fetched stats; now filters MessageQueue by brokerName + topic + queueId.
    • resolveBrokerName(MessageExt): new method — resolves broker name via topic route data + IP-level matching, with single-broker short-circuit.
  • MessageServiceImplTest.java: 9 unit tests covering the fallback logic.

Verifying this change

Unit tests added (MessageServiceImplTest):

  1. testMessageTrackDetail_NotConsumeYetCorrectedToConsumed — NOT_CONSUME_YET with consumerOffset > queueOffset → corrected to CONSUMED
  2. testMessageTrackDetail_NotConsumeYetRemainsWhenNotConsumed — NOT_CONSUME_YET with consumerOffset < queueOffset → remains NOT_CONSUME_YET
  3. testMessageTrackDetail_CrossBrokerNoFalsePositive — multi-broker route, msg on broker-a, broker-b has same topic+queueId with higher offset → stays NOT_CONSUME_YET (no cross-broker match)
  4. testMessageTrackDetail_BrokerNameUnresolvableStaysNotConsumeYet — multi-broker route, no broker IP matches → stays NOT_CONSUME_YET (conservative fallback)
  5. testMessageTrackDetail_ConsumedTrackUnchanged — CONSUMED track is not re-verified
  6. testMessageTrackDetail_NotOnlineTrackUnchanged — NOT_ONLINE track is not re-verified
  7. testMessageTrackDetail_ExamineConsumeStatsThrowsException — when examineConsumeStats throws, original NOT_CONSUME_YET is preserved
  8. testMessageTrackDetail_GroupLevelStatsCache — two NOT_CONSUME_YET tracks with same group → examineConsumeStats called only once
  9. testMessageTrackDetail_SingleBrokerShortCircuitWithMismatchedIp — single broker with non-matching hostname → short-circuit resolves brokerName, corrected to CONSUMED

All 24 tests in MessageServiceImplTest pass.

  • Make sure there is a Github issue filed for the change.
  • Format the pull request title like [ISSUE #380] Fix NOT_CONSUME_YET false positive in message track detail.
  • Write a pull request description that is detailed enough.
  • Write necessary unit-test to verify your logic correction.
  • Run mvn clean install -DskipITs to make sure unit-test pass.
  • If this contribution is large, please file an Apache Individual License Agreement.

…k detail

The messageTrackDetail() method delegates to DefaultMQAdminExtImpl.consumed()
which requires an exact broker-address match between msg.getStoreHost() and
the broker's registered address. When the broker registers with a hostname
(or when DNS resolution inside the dashboard container differs from the
broker), the address comparison silently fails and every message is
incorrectly reported as NOT_CONSUME_YET even though it has already been
consumed.

This patch adds a fallback verification in MessageServiceImpl: when a track
is marked NOT_CONSUME_YET, it re-checks the consumer offset directly via
examineConsumeStats, matching only on topic + queueId + offset and skipping
the fragile address comparison. If the consumer offset has advanced past
the message's queue offset, the track type is corrected to CONSUMED.
Copilot AI review requested due to automatic review settings July 8, 2026 11:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a RocketMQ Dashboard UI false-positive where already-consumed messages can be shown as NOT_CONSUME_YET in the message track detail view by adding a fallback verification that checks consumer offsets via examineConsumeStats when the admin API reports NOT_CONSUME_YET.

Changes:

  • Post-process messageTrackDetail() results to re-verify NOT_CONSUME_YET tracks using consumer offsets (avoiding broker-address equality pitfalls).
  • Add unit tests covering correction behavior, non-correction cases, and exception handling during fallback.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/main/java/org/apache/rocketmq/dashboard/service/impl/MessageServiceImpl.java Adds fallback consumption verification for NOT_CONSUME_YET tracks using examineConsumeStats.
src/test/java/org/apache/rocketmq/dashboard/service/impl/MessageServiceImplTest.java Adds unit tests validating the new fallback logic and its failure behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/org/apache/rocketmq/dashboard/service/impl/MessageServiceImpl.java Outdated
Comment thread src/main/java/org/apache/rocketmq/dashboard/service/impl/MessageServiceImpl.java Outdated
…level stats cache

- Add resolveBrokerName() to match message storeHost against topic route data,
  preventing cross-broker false positives when multiple brokers share same topic+queueId
- Change isConsumedByGroup() to accept ConsumeStats instead of group name,
  filtering MessageQueue by brokerName+topic+queueId for precise offset comparison
- Add group-level ConsumeStats cache via computeIfAbsent to avoid redundant RPC calls
- Return false (conservative) when brokerName cannot be resolved
- Add 3 new test cases: cross-broker no-false-positive, unresolvable brokerName,
  and group-level cache verification
@messere1
messere1 force-pushed the contest/bugfix-pr07-not-consume-yet branch from cafa441 to 441ed79 Compare July 8, 2026 12:16
…ismatch scenarios

When topic route data contains only one broker, skip IP-level address
matching and directly adopt that brokerName. This covers the most common
deployment (dev/test/small prod) where DNS/hostname differences between
broker registration and dashboard JVM cause resolveBrokerName to fail
silently — the exact root cause of issue apache#380.

- resolveBrokerName: early return when brokerDatas.size() == 1
- New test: SingleBrokerShortCircuitWithMismatchedIp verifies that a
  single broker with non-matching hostname still resolves correctly
- Updated test: BrokerNameUnresolvable now uses multi-broker route data
  to properly test the conservative fallback path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

消息已经消费了,为什么显示还是NOT_CONSUME_YET

3 participants