Before Creating the Bug Report
Runtime platform environment
- OS: Linux
- Component: Broker (
PopLongPollingService + DefaultMessageStore.ReputMessageService)
RocketMQ version
- branch: develop
- Git commit id: e348efa
JDK Version
JDK 8
Describe the Bug
PopLongPollingService#notifyMessageArrivingFromRetry dereferences the dispatch request's property map without a null check:
private void notifyMessageArrivingFromRetry(String topic, int queueId, Long tagsCode, long msgStoreTime,
byte[] filterBitMap, Map<String, String> properties) {
String prefix = MixAll.RETRY_GROUP_TOPIC_PREFIX;
String originGroup = properties.get(MessageConst.PROPERTY_ORIGIN_GROUP); // NPE when properties == null
A DispatchRequest legitimately carries a null properties map: MessageDecoder.string2messageProperties returns null for a message stored without properties (e.g. a message written by a non-Java client, or through any path that stores a message on a %RETRY%-prefixed topic without user properties). The store's own code acknowledges this — DefaultMessageStore#notifyMessageArrive4MultiQueue explicitly guards prop == null, and PullRequestHoldService#notifyMessageArriving guards properties != null — but the pop retry branch does not.
Why this is severe: the listener is invoked from DefaultMessageStore.ReputMessageService#doReput before reputFromOffset is advanced, and doReput only catches RocksDBException. The resulting NullPointerException propagates to ServiceThread.run, which logs and loops — then re-reads the same commitlog record and throws again, forever. The broker stops dispatching all messages (consume queues stop advancing, no long-polling wakeups, consumers see a full outage) while flooding the log at ~1000 lines/s. A single poison message is enough; it stays poisoned across restarts because reputFromOffset is recovered from the consume queue state.
Steps to Reproduce
- On a broker with pop enabled, store a message with no properties on a topic named
%RETRY%<group> (any client able to write that topic; the properties map in the dispatch request is then null).
- Watch the reput thread loop on
service has exception. NullPointerException at PopLongPollingService.notifyMessageArrivingFromRetry, with reputFromOffset frozen.
In a unit test, calling notifyMessageArrivingWithRetryTopic("%RETRY%g", -1, -1, -1L, 0L, null, null) throws NPE on current develop.
What Did You Expect to See?
The notification is skipped (there is no origin group to wake up), and dispatch continues.
What Did You See Instead?
NPE in the reput thread → the same message is re-dispatched forever → broker-wide dispatch stall.
Additional Context
Fix: return early when properties == null (a retry topic message without properties can't be mapped back to an origin group, so there is nothing to wake up), consistent with the existing guards in PullRequestHoldService and notifyMessageArrive4MultiQueue. I will submit a PR with a regression test.
Before Creating the Bug Report
Runtime platform environment
PopLongPollingService+DefaultMessageStore.ReputMessageService)RocketMQ version
JDK Version
JDK 8
Describe the Bug
PopLongPollingService#notifyMessageArrivingFromRetrydereferences the dispatch request's property map without a null check:A
DispatchRequestlegitimately carries a null properties map:MessageDecoder.string2messagePropertiesreturns null for a message stored without properties (e.g. a message written by a non-Java client, or through any path that stores a message on a%RETRY%-prefixed topic without user properties). The store's own code acknowledges this —DefaultMessageStore#notifyMessageArrive4MultiQueueexplicitly guardsprop == null, andPullRequestHoldService#notifyMessageArrivingguardsproperties != null— but the pop retry branch does not.Why this is severe: the listener is invoked from
DefaultMessageStore.ReputMessageService#doReputbeforereputFromOffsetis advanced, anddoReputonly catchesRocksDBException. The resultingNullPointerExceptionpropagates toServiceThread.run, which logs and loops — then re-reads the same commitlog record and throws again, forever. The broker stops dispatching all messages (consume queues stop advancing, no long-polling wakeups, consumers see a full outage) while flooding the log at ~1000 lines/s. A single poison message is enough; it stays poisoned across restarts becausereputFromOffsetis recovered from the consume queue state.Steps to Reproduce
%RETRY%<group>(any client able to write that topic; the properties map in the dispatch request is then null).service has exception. NullPointerExceptionatPopLongPollingService.notifyMessageArrivingFromRetry, withreputFromOffsetfrozen.In a unit test, calling
notifyMessageArrivingWithRetryTopic("%RETRY%g", -1, -1, -1L, 0L, null, null)throws NPE on current develop.What Did You Expect to See?
The notification is skipped (there is no origin group to wake up), and dispatch continues.
What Did You See Instead?
NPE in the reput thread → the same message is re-dispatched forever → broker-wide dispatch stall.
Additional Context
Fix: return early when
properties == null(a retry topic message without properties can't be mapped back to an origin group, so there is nothing to wake up), consistent with the existing guards inPullRequestHoldServiceandnotifyMessageArrive4MultiQueue. I will submit a PR with a regression test.