This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git


The following commit(s) were added to refs/heads/develop by this push:
     new b764205990 [ISSUE #11272] Defer LMQ arrival notification to 
post-commit (#11273)
b764205990 is described below

commit b76420599098c6f53bdf80544f8a1b7ffe678fa9
Author: imzs <[email protected]>
AuthorDate: Mon Oct 12 13:36:26 2026 +0800

    [ISSUE #11272] Defer LMQ arrival notification to post-commit (#11273)
---
 .../apache/rocketmq/store/DefaultMessageStore.java |  23 +++-
 .../rocketmq/store/DefaultMessageStoreTest.java    | 126 +++++++++++++++++----
 2 files changed, 126 insertions(+), 23 deletions(-)

diff --git 
a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java 
b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
index e2d95b963d..3bd6a95a2b 100644
--- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
@@ -112,6 +112,7 @@ import 
org.apache.rocketmq.store.queue.ConsumeQueueInterface;
 import org.apache.rocketmq.store.queue.ConsumeQueueStore;
 import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface;
 import org.apache.rocketmq.store.queue.CqUnit;
+import org.apache.rocketmq.store.queue.MultiDispatchUtils;
 import org.apache.rocketmq.store.queue.ReferredIterator;
 import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage;
 import org.apache.rocketmq.store.stats.BrokerStatsManager;
@@ -2642,13 +2643,24 @@ public class DefaultMessageStore implements 
MessageStore {
 
     @Override
     public void notifyMessageArriveIfNecessary(DispatchRequest 
dispatchRequest) {
+        // keeps original parameters and behavior by always passing true here
+        notifyMessageArrive(dispatchRequest, true);
+    }
+
+    /**
+     * Notify message arrival, optionally including the multi-queue (LMQ) part.
+     * @param notifyMultiQueue whether to also notify the multi-queue arrival
+     */
+    private void notifyMessageArrive(DispatchRequest dispatchRequest, boolean 
notifyMultiQueue) {
         if (DefaultMessageStore.this.brokerConfig.isLongPollingEnable()
             && DefaultMessageStore.this.messageArrivingListener != null) {
             
DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
                 dispatchRequest.getQueueId(), 
dispatchRequest.getConsumeQueueOffset() + 1,
                 dispatchRequest.getTagsCode(), 
dispatchRequest.getStoreTimestamp(),
                 dispatchRequest.getBitMap(), 
dispatchRequest.getPropertiesMap());
-            
DefaultMessageStore.this.reputMessageService.notifyMessageArrive4MultiQueue(dispatchRequest);
+            if (notifyMultiQueue) {
+                
DefaultMessageStore.this.reputMessageService.notifyMessageArrive4MultiQueue(dispatchRequest);
+            }
         }
     }
 
@@ -2745,7 +2757,10 @@ public class DefaultMessageStore implements MessageStore 
{
                                 
DefaultMessageStore.this.doDispatch(dispatchRequest);
 
                                 if (isNotifyMessageArriveWhenReput()) {
-                                    
notifyMessageArriveIfNecessary(dispatchRequest);
+                                    // only notify multi-queue when we do real 
LMQ dispatch action.
+                                    boolean notifyMultiQueue = 
MultiDispatchUtils.checkMultiDispatchQueue(
+                                        
DefaultMessageStore.this.messageStoreConfig, dispatchRequest);
+                                    notifyMessageArrive(dispatchRequest, 
notifyMultiQueue);
                                 }
 
                                 this.reputFromOffset += size;
@@ -2929,7 +2944,9 @@ public class DefaultMessageStore implements MessageStore {
                     for (DispatchRequest dispatchRequest : dispatchRequests) {
                         DefaultMessageStore.this.doDispatch(dispatchRequest);
                         // wake up long-polling
-                        
DefaultMessageStore.this.notifyMessageArriveIfNecessary(dispatchRequest);
+                        boolean notifyMultiQueue = 
MultiDispatchUtils.checkMultiDispatchQueue(
+                            DefaultMessageStore.this.messageStoreConfig, 
dispatchRequest);
+                        notifyMessageArrive(dispatchRequest, notifyMultiQueue);
 
                         if 
(!DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() &&
                             
DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == 
BrokerRole.SLAVE) {
diff --git 
a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java 
b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
index 05e186537b..91289af9ef 100644
--- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
+++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
@@ -20,13 +20,17 @@ package org.apache.rocketmq.store;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -59,8 +63,12 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.rocketmq.common.BrokerConfig;
 import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.TopicConfig;
 import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.attribute.TopicMessageType;
+import org.apache.rocketmq.common.message.MessageAccessor;
 import org.apache.rocketmq.common.message.MessageBatch;
+import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.common.message.MessageDecoder;
 import org.apache.rocketmq.common.message.MessageExt;
 import org.apache.rocketmq.common.message.MessageExtBatch;
@@ -83,6 +91,8 @@ import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DefaultMessageStoreTest {
+    private static final String DEFAULT_STORE_PATH_ROOT_DIR = new 
MessageStoreConfig().getStorePathRootDir();
+
     private final String storeMessage = "Once, there was a chance for me!";
     private final String messageTopic = "FooBar";
     private int queueTotal = 100;
@@ -131,35 +141,39 @@ public class DefaultMessageStoreTest {
 
     @After
     public void destroy() {
-        messageStore.shutdown();
-        messageStore.destroy();
+        shutdownAndDeleteStore(messageStore);
+    }
 
-        MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
-        File file = new File(messageStoreConfig.getStorePathRootDir());
-        UtilAll.deleteFile(file);
+    private void shutdownAndDeleteStore(MessageStore store) {
+        store.shutdown();
+        store.destroy();
+        UtilAll.deleteFile(new 
File(store.getMessageStoreConfig().getStorePathRootDir()));
     }
 
     private MessageStore buildMessageStore() throws Exception {
-        return buildMessageStore(null);
+        return buildMessageStore(null, null);
     }
 
     private MessageStore buildMessageStore(String storePathRootDir) throws 
Exception {
-        MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
-        messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10);
-        messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10);
-        messageStoreConfig.setMaxHashSlotNum(10000);
-        messageStoreConfig.setMaxIndexNum(100 * 100);
-        messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH);
-        messageStoreConfig.setFlushIntervalConsumeQueue(1);
-        messageStoreConfig.setHaListenPort(0);
-        if (Strings.isNullOrEmpty(storePathRootDir)) {
-            UUID uuid = UUID.randomUUID();
-            storePathRootDir = System.getProperty("java.io.tmpdir") + 
File.separator + "store-" + uuid.toString();
+        MessageStoreConfig storeConfig = new MessageStoreConfig();
+        storeConfig.setStorePathRootDir(storePathRootDir);
+        return buildMessageStore(storeConfig, null);
+    }
+
+    private MessageStore buildMessageStore(MessageStoreConfig storeConfig, 
MessageArrivingListener listener) throws Exception {
+        if (storeConfig == null) {
+            storeConfig = new MessageStoreConfig();
         }
-        messageStoreConfig.setStorePathRootDir(storePathRootDir);
-        return new DefaultMessageStore(messageStoreConfig,
+        if (Strings.isNullOrEmpty(storeConfig.getStorePathRootDir())
+            || 
DEFAULT_STORE_PATH_ROOT_DIR.equals(storeConfig.getStorePathRootDir())) {
+            
storeConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + 
File.separator + "store-" + UUID.randomUUID());
+        }
+        storeConfig.setHaListenPort(0);
+        storeConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH);
+        storeConfig.setFlushIntervalConsumeQueue(1);
+        return new DefaultMessageStore(storeConfig,
             new BrokerStatsManager("simpleTest", true),
-            new MyMessageArrivingListener(),
+            listener != null ? listener : new MyMessageArrivingListener(),
             new BrokerConfig(), new ConcurrentHashMap<>());
     }
 
@@ -1118,6 +1132,78 @@ public class DefaultMessageStoreTest {
         store.destroy();
     }
 
+    @Test
+    public void testReputNotify_notifyMultiQueue() throws Exception {
+        String lmq = "%LMQ%notify";
+        messageBody = storeMessage.getBytes();
+        MessageArrivingListener listener = mock(MessageArrivingListener.class);
+
+        MessageStoreConfig storeConfig = new MessageStoreConfig();
+        storeConfig.setEnableLmq(true);
+        storeConfig.setEnableMultiDispatch(true);
+
+        DefaultMessageStore store = (DefaultMessageStore) 
buildMessageStore(storeConfig, listener);
+        assertTrue(store.load());
+        store.start();
+        try {
+            store.putMessage(buildLmqMessage(lmq));
+            StoreTestUtil.waitCommitLogReput(store);
+            assertThat(store.getConsumeQueueTable().get(lmq)).isNotNull();
+            verify(listener).arriving(eq(lmq), eq(MixAll.LMQ_QUEUE_ID), 
eq(1L), anyLong(), anyLong(), any(), any());
+        } finally {
+            shutdownAndDeleteStore(store);
+        }
+    }
+
+    @Test
+    public void testReputNotify_deferNotifyToGroupCommitForLite() throws 
Exception {
+        String lmq = "%LMQ%notify";
+        messageBody = storeMessage.getBytes();
+        MessageArrivingListener listener = mock(MessageArrivingListener.class);
+
+        MessageStoreConfig storeConfig = new MessageStoreConfig();
+        storeConfig.setEnableLmq(true);
+        storeConfig.setEnableMultiDispatch(true);
+        // Combine store where the LMQ consume queue is committed 
asynchronously by RocksGroupCommitService
+        storeConfig.setRocksdbCQDoubleWriteEnable(true);
+        storeConfig.setRocksdbCQSelectiveDoubleWriteEnable(true);
+        storeConfig.setCombineCQUseRocksdbForLmq(true);
+
+        DefaultMessageStore store = (DefaultMessageStore) 
buildMessageStore(storeConfig, listener);
+        // selective double-write only routes LITE topics to rocksdb
+        TopicConfig liteTopic = new TopicConfig(messageTopic);
+        liteTopic.setTopicMessageType(TopicMessageType.LITE);
+        store.getTopicConfigs().put(messageTopic, liteTopic);
+        assertTrue(store.load());
+        store.start();
+        try {
+            store.putMessage(buildLmqMessage(lmq));
+            StoreTestUtil.waitCommitLogReput(store);
+
+            verify(listener, timeout(5000).times(1))
+                .arriving(eq(lmq), eq(MixAll.LMQ_QUEUE_ID), eq(1L), anyLong(), 
anyLong(), any(), any());
+            verify(listener, times(2)).arriving(eq(messageTopic), eq(0), 
eq(1L), anyLong(), anyLong(), any(), any());
+        } finally {
+            shutdownAndDeleteStore(store);
+        }
+    }
+
+    private MessageExtBrokerInner buildLmqMessage(String lmq) {
+        MessageExtBrokerInner msg = new MessageExtBrokerInner();
+        msg.setTopic(messageTopic);
+        msg.setTags("TAG1");
+        msg.setKeys("Hello");
+        msg.setBody(messageBody);
+        msg.setQueueId(0);
+        msg.setSysFlag(0);
+        msg.setBornTimestamp(System.currentTimeMillis());
+        msg.setStoreHost(storeHost);
+        msg.setBornHost(bornHost);
+        MessageAccessor.putProperty(msg, 
MessageConst.PROPERTY_INNER_MULTI_DISPATCH, lmq);
+        
msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
+        return msg;
+    }
+
     private class MyMessageArrivingListener implements MessageArrivingListener 
{
         @Override
         public void arriving(String topic, int queueId, long logicOffset, long 
tagsCode, long msgStoreTime,

Reply via email to