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 80e1ae5577 [ISSUE #11163] Fix lite topic prefix index not maintained 
on first message (#11164)
80e1ae5577 is described below

commit 80e1ae55773c2330d2005b86f020ed028f649e94
Author: Quan <[email protected]>
AuthorDate: Tue Sep 15 10:03:56 2026 +0800

    [ISSUE #11163] Fix lite topic prefix index not maintained on first message 
(#11164)
    
    - Correct the off-by-one offset check in LiteEventDispatcher.dispatch 
(offset == 0 -> == 1) so onLmqCreate fires on the first arriving message
    - Restore store-dependent lifecycle tests to the concrete impl test 
classes, driven by real putMessage through the notify path
    - Add MessageArrivingListener overloads to LiteTestUtil to wire the real 
dispatch path in tests
---
 .../rocketmq/broker/lite/LiteEventDispatcher.java  |   7 +-
 .../lite/AbstractLiteLifecycleManagerTest.java     | 108 ---------------------
 .../broker/lite/LiteLifecycleManagerTest.java      |  93 +++++++++++++++++-
 .../apache/rocketmq/broker/lite/LiteTestUtil.java  |  19 +++-
 .../lite/RocksDBLiteLifecycleManagerTest.java      | 107 ++++++++++++++++++--
 5 files changed, 209 insertions(+), 125 deletions(-)

diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/lite/LiteEventDispatcher.java 
b/broker/src/main/java/org/apache/rocketmq/broker/lite/LiteEventDispatcher.java
index 7018a46344..d7c064d242 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/lite/LiteEventDispatcher.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/lite/LiteEventDispatcher.java
@@ -90,9 +90,10 @@ public class LiteEventDispatcher extends ServiceThread {
         if (queueId != 0 || !LiteUtil.isLiteTopicQueue(lmqName)) {
             return;
         }
-        // Maintain prefix index only on the lmq's first message; pre-existing 
lmqs are
-        // populated once at startup during init().
-        if (offset == 0) {
+        // Maintain prefix index on the lmq's first message: the arriving 
notification carries
+        // logicOffset = queueOffset + 1, so the first message shows offset == 
1. Pre-existing lmqs
+        // are populated once at startup during init().
+        if (offset == 1) {
             liteLifecycleManager.onLmqCreate(lmqName);
         }
         doDispatch(group, lmqName, null);
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManagerTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManagerTest.java
index b3eb91c373..4a15311a79 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManagerTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/AbstractLiteLifecycleManagerTest.java
@@ -18,9 +18,7 @@
 package org.apache.rocketmq.broker.lite;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -47,7 +45,6 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -59,7 +56,6 @@ import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.atLeastOnce;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -112,7 +108,6 @@ public class AbstractLiteLifecycleManagerTest {
         topicConfig.setLiteTopicExpiration(1);
         topicConfigTable.put(PARENT_TOPIC, topicConfig);
         
when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable);
-        
when(topicConfigManager.selectTopicConfig(PARENT_TOPIC)).thenReturn(topicConfig);
 
         groupConfig.setGroupName(GROUP);
         groupConfig.setLiteBindTopic(PARENT_TOPIC);
@@ -158,28 +153,6 @@ public class AbstractLiteLifecycleManagerTest {
         Assert.assertFalse(lifecycleManager.isLmqExist("whatever"));
     }
 
-    @Test
-    public void testGetLiteTopicCount() {
-        Assert.assertEquals(1, 
lifecycleManager.getLiteTopicCount(PARENT_TOPIC));
-        Assert.assertEquals(0, lifecycleManager.getLiteTopicCount("whatever"));
-
-        // parentTopic1: 2 liteTopics, parentTopic2: 3 liteTopics
-        String parent1 = "parentTopic1";
-        String parent2 = "parentTopic2";
-        registerLiteTopicConfig(parent1);
-        registerLiteTopicConfig(parent2);
-        lifecycleManager.lmqPrefixIndex.add(LiteUtil.toLmqName(parent1, 
"sub1"));
-        lifecycleManager.lmqPrefixIndex.add(LiteUtil.toLmqName(parent1, 
"sub2"));
-        lifecycleManager.lmqPrefixIndex.add(LiteUtil.toLmqName(parent2, 
"sub1"));
-        lifecycleManager.lmqPrefixIndex.add(LiteUtil.toLmqName(parent2, 
"sub2"));
-        lifecycleManager.lmqPrefixIndex.add(LiteUtil.toLmqName(parent2, 
"sub3"));
-
-        Assert.assertEquals(2, lifecycleManager.getLiteTopicCount(parent1));
-        Assert.assertEquals(3, lifecycleManager.getLiteTopicCount(parent2));
-        // PARENT_TOPIC count unchanged
-        Assert.assertEquals(1, 
lifecycleManager.getLiteTopicCount(PARENT_TOPIC));
-    }
-
     @Test
     public void testIsLiteTopicExpired() {
         // not lite topic queue
@@ -268,79 +241,6 @@ public class AbstractLiteLifecycleManagerTest {
         verify(liteSubscriptionRegistry).cleanSubscription(EXIST_LMQ_NAME, 
false);
     }
 
-    @Test
-    public void testCleanByParentTopic() {
-        String lmq1 = LiteUtil.toLmqName(PARENT_TOPIC, "sub1");
-        String lmq2 = LiteUtil.toLmqName(PARENT_TOPIC, "sub2");
-        String lmq3 = LiteUtil.toLmqName(PARENT_TOPIC, "sub3");
-
-        String otherLmq1 = LiteUtil.toLmqName("otherParentTopic", "sub1");
-        String otherLmq2 = LiteUtil.toLmqName("otherParentTopic", "sub2");
-
-        // multiple LMQs: deleteLmq called only for LMQs under PARENT_TOPIC
-        lifecycleManager.lmqPrefixIndex.remove(EXIST_LMQ_NAME);
-        lifecycleManager.lmqPrefixIndex.add(lmq1);
-        lifecycleManager.lmqPrefixIndex.add(lmq2);
-        lifecycleManager.lmqPrefixIndex.add(lmq3);
-        lifecycleManager.lmqPrefixIndex.add(otherLmq1);
-        lifecycleManager.lmqPrefixIndex.add(otherLmq2);
-
-        ArgumentCaptor<String> parentCaptor = 
ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<String> lmqCaptor = 
ArgumentCaptor.forClass(String.class);
-        lifecycleManager.cleanByParentTopic(PARENT_TOPIC);
-        verify(lifecycleManager, times(3)).deleteLmq(parentCaptor.capture(), 
lmqCaptor.capture());
-        
Assert.assertTrue(parentCaptor.getAllValues().stream().allMatch(PARENT_TOPIC::equals));
-        Assert.assertEquals(new HashSet<>(Arrays.asList(lmq1, lmq2, lmq3)), 
new HashSet<>(lmqCaptor.getAllValues()));
-
-        // other parent's LMQs remain untouched
-        List<String> otherResult = 
lifecycleManager.collectByParentTopic("otherParentTopic");
-        Assert.assertEquals(new HashSet<>(Arrays.asList(otherLmq1, 
otherLmq2)), new HashSet<>(otherResult));
-
-        // zero LMQs: deleteLmq not called
-        Mockito.clearInvocations(lifecycleManager);
-        lifecycleManager.cleanByParentTopic(PARENT_TOPIC);
-        verify(lifecycleManager, never()).deleteLmq(anyString(), anyString());
-
-        // guard: non-lite topic and null both return early
-        Mockito.clearInvocations(lifecycleManager);
-        lifecycleManager.lmqPrefixIndex.add(EXIST_LMQ_NAME);
-        lifecycleManager.cleanByParentTopic("nonExistentTopic");
-        verify(lifecycleManager, never()).deleteLmq(anyString(), anyString());
-        lifecycleManager.cleanByParentTopic(null);
-        verify(lifecycleManager, never()).deleteLmq(anyString(), anyString());
-    }
-
-    @Test
-    public void testCollectByParentTopic() {
-        String lmq1 = LiteUtil.toLmqName(PARENT_TOPIC, "sub1");
-        String lmq2 = LiteUtil.toLmqName(PARENT_TOPIC, "sub2");
-        String lmq3 = LiteUtil.toLmqName(PARENT_TOPIC, "sub3");
-
-        String otherLmq1 = LiteUtil.toLmqName("otherParentTopic", "sub1");
-        String otherLmq2 = LiteUtil.toLmqName("otherParentTopic", "sub2");
-
-        lifecycleManager.lmqPrefixIndex.remove(EXIST_LMQ_NAME);
-        lifecycleManager.lmqPrefixIndex.add(lmq1);
-        lifecycleManager.lmqPrefixIndex.add(lmq2);
-        lifecycleManager.lmqPrefixIndex.add(lmq3);
-        lifecycleManager.lmqPrefixIndex.add(otherLmq1);
-        lifecycleManager.lmqPrefixIndex.add(otherLmq2);
-
-        // multiple LMQs: returns only those under PARENT_TOPIC, excluding 
other parent's
-        List<String> result = 
lifecycleManager.collectByParentTopic(PARENT_TOPIC);
-        Assert.assertEquals(new HashSet<>(Arrays.asList(lmq1, lmq2, lmq3)), 
new HashSet<>(result));
-
-        // no LMQs under parent: returns empty list
-        result = lifecycleManager.collectByParentTopic("nonExistentTopic");
-        Assert.assertTrue(result.isEmpty());
-
-        // guard: null and empty both return empty list
-        result = lifecycleManager.collectByParentTopic(null);
-        Assert.assertTrue(result.isEmpty());
-        result = lifecycleManager.collectByParentTopic("");
-        Assert.assertTrue(result.isEmpty());
-    }
-
     @Test
     public void testRun() throws InterruptedException {
         brokerConfig.setLiteTtlCheckInterval(100L);
@@ -357,14 +257,6 @@ public class AbstractLiteLifecycleManagerTest {
         verify(liteSubscriptionRegistry, 
atLeastOnce()).cleanSubscription(EXIST_LMQ_NAME, false);
     }
 
-    private void registerLiteTopicConfig(String parentTopic) {
-        TopicConfig config = new TopicConfig(parentTopic, 1, 1);
-        config.getAttributes().put(
-            TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), 
TopicMessageType.LITE.getValue());
-        topicConfigTable.put(parentTopic, config);
-        
when(topicConfigManager.selectTopicConfig(parentTopic)).thenReturn(config);
-    }
-
     private static class TestLiteLifecycleManager extends 
AbstractLiteLifecycleManager {
 
         public TestLiteLifecycleManager(BrokerController brokerController, 
LiteSharding liteSharding) {
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteLifecycleManagerTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteLifecycleManagerTest.java
index e936fc1805..cf5d872d88 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteLifecycleManagerTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteLifecycleManagerTest.java
@@ -18,13 +18,20 @@
 package org.apache.rocketmq.broker.lite;
 
 import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.longpolling.NotifyMessageArrivingListener;
+import org.apache.rocketmq.broker.longpolling.PullRequestHoldService;
 import org.apache.rocketmq.broker.offset.ConsumerOffsetManager;
+import org.apache.rocketmq.broker.processor.NotificationProcessor;
+import org.apache.rocketmq.broker.processor.PopMessageProcessor;
 import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager;
 import org.apache.rocketmq.broker.topic.TopicConfigManager;
 import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicAttributes;
 import org.apache.rocketmq.common.TopicConfig;
 import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.attribute.TopicMessageType;
 import org.apache.rocketmq.common.lite.LiteUtil;
+import org.apache.rocketmq.store.MessageArrivingListener;
 import org.apache.rocketmq.store.MessageStore;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -36,6 +43,7 @@ import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
@@ -49,6 +57,7 @@ import static org.awaitility.Awaitility.await;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.Silent.class)
@@ -59,6 +68,7 @@ public class LiteLifecycleManagerTest {
     private static String storePathRootDir;
     private static MessageStore messageStore;
     private static LiteLifecycleManager liteLifecycleManager;
+    private static LiteEventDispatcher liteEventDispatcher;
     private static TopicConfig mockTopicConfig = new TopicConfig();
 
     @BeforeClass
@@ -66,10 +76,6 @@ public class LiteLifecycleManagerTest {
         storePathRootDir = System.getProperty("java.io.tmpdir") + 
File.separator + "store-lifecycleTest";
         UtilAll.deleteFile(new File(storePathRootDir));
 
-        messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, 
BROKER_CONFIG, TOPIC_CONFIG_TABLE, false);
-        messageStore.load();
-        messageStore.start();
-
         BrokerController brokerController = 
Mockito.mock(BrokerController.class);
         LiteSharding liteSharding = Mockito.mock(LiteSharding.class);
         TopicConfigManager topicConfigManager = 
Mockito.mock(TopicConfigManager.class);
@@ -77,9 +83,11 @@ public class LiteLifecycleManagerTest {
         LiteSubscriptionRegistry liteSubscriptionRegistry = 
Mockito.mock(LiteSubscriptionRegistry.class);
         ConsumerOffsetManager consumerOffsetManager = 
Mockito.mock(ConsumerOffsetManager.class);
         when(consumerOffsetManager.getPullOffsetTable()).thenReturn(new 
ConcurrentHashMap<>());
+        // dispatch() fans out to subscribers right after maintaining the 
prefix index; no subscriber here
+        
when(liteSubscriptionRegistry.getAllSubscribers(nullable(String.class), 
anyString()))
+            .thenReturn(Collections.emptyMap());
 
         when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG);
-        when(brokerController.getMessageStore()).thenReturn(messageStore);
         
when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager);
         
when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager);
         
when(brokerController.getLiteSubscriptionRegistry()).thenReturn(liteSubscriptionRegistry);
@@ -90,6 +98,20 @@ public class LiteLifecycleManagerTest {
 
         LiteLifecycleManager testObject = new 
LiteLifecycleManager(brokerController, liteSharding);
         liteLifecycleManager = Mockito.spy(testObject);
+
+        // Wire the real notify path so putMessage drives the prefix index:
+        //   putMessage -> reput -> NotifyMessageArrivingListener.arriving
+        //   -> LiteEventDispatcher.dispatch -> onLmqCreate -> lmqPrefixIndex
+        liteEventDispatcher = new LiteEventDispatcher(brokerController, 
liteSubscriptionRegistry, liteLifecycleManager);
+        MessageArrivingListener listener = new NotifyMessageArrivingListener(
+            Mockito.mock(PullRequestHoldService.class), 
Mockito.mock(PopMessageProcessor.class),
+            Mockito.mock(NotificationProcessor.class), liteEventDispatcher);
+
+        messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, 
BROKER_CONFIG, TOPIC_CONFIG_TABLE, false, listener);
+        when(brokerController.getMessageStore()).thenReturn(messageStore);
+        messageStore.load();
+        messageStore.start();
+
         liteLifecycleManager.init();
     }
 
@@ -112,6 +134,67 @@ public class LiteLifecycleManagerTest {
         Assert.assertEquals(0, 
liteLifecycleManager.getMaxOffsetInQueue(UUID.randomUUID().toString()));
     }
 
+    @Test
+    public void testCollectByParentTopic() {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
UUID.randomUUID().toString()));
+            
messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), 
UUID.randomUUID().toString()));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+        List<String> result = 
liteLifecycleManager.collectByParentTopic(parentTopic);
+        Assert.assertEquals(num, result.size());
+        for (String lmqName : result) {
+            Assert.assertTrue(LiteUtil.belongsTo(lmqName, parentTopic));
+        }
+
+        result = 
liteLifecycleManager.collectByParentTopic(UUID.randomUUID().toString());
+        Assert.assertEquals(0, result.size());
+    }
+
+    @Test
+    public void testGetLiteTopicCount() {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        mockTopicConfig.getAttributes().put(
+            TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), 
TopicMessageType.LITE.getValue());
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
UUID.randomUUID().toString()));
+            
messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), 
UUID.randomUUID().toString()));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+
+        Assert.assertEquals(num, 
liteLifecycleManager.getLiteTopicCount(parentTopic));
+        Assert.assertEquals(0, 
liteLifecycleManager.getLiteTopicCount(UUID.randomUUID().toString()));
+    }
+
+    @Test
+    public void testCleanByParentTopic() {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        mockTopicConfig.getAttributes().put(
+            TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), 
TopicMessageType.LITE.getValue());
+        List<String> liteTopics =
+            IntStream.range(0, num).mapToObj(i -> 
UUID.randomUUID().toString()).collect(Collectors.toList());
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
liteTopics.get(i)));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+
+        for (int i = 0; i < num; i++) {
+            String lmqName = LiteUtil.toLmqName(parentTopic, 
liteTopics.get(i));
+            
Assert.assertTrue(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName));
+        }
+
+        liteLifecycleManager.cleanByParentTopic(parentTopic);
+
+        for (int i = 0; i < num; i++) {
+            String lmqName = LiteUtil.toLmqName(parentTopic, 
liteTopics.get(i));
+            
Assert.assertFalse(messageStore.getQueueStore().getConsumeQueueTable().containsKey(lmqName));
+        }
+    }
+
     @Ignore
     @Test
     public void testCleanExpiredLiteTopic() {
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteTestUtil.java 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteTestUtil.java
index ec6efb1fd5..2a1a3ce1f8 100644
--- a/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteTestUtil.java
+++ b/broker/src/test/java/org/apache/rocketmq/broker/lite/LiteTestUtil.java
@@ -26,6 +26,7 @@ import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.common.message.MessageDecoder;
 import org.apache.rocketmq.common.message.MessageExtBrokerInner;
 import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.MessageArrivingListener;
 import org.apache.rocketmq.store.MessageStore;
 import org.apache.rocketmq.store.RocksDBMessageStore;
 import org.apache.rocketmq.store.config.FlushDiskType;
@@ -40,19 +41,31 @@ public class LiteTestUtil {
     public static MessageStore buildMessageStore(final BrokerConfig 
brokerConfig,
         MessageStoreConfig storeConfig, final ConcurrentMap<String, 
TopicConfig> topicConfigTable,
         boolean isRocksDBStore) throws Exception {
+        return buildMessageStore(brokerConfig, storeConfig, topicConfigTable, 
isRocksDBStore, null);
+    }
+
+    public static MessageStore buildMessageStore(final BrokerConfig 
brokerConfig,
+        MessageStoreConfig storeConfig, final ConcurrentMap<String, 
TopicConfig> topicConfigTable,
+        boolean isRocksDBStore, MessageArrivingListener 
messageArrivingListener) throws Exception {
 
         BrokerStatsManager brokerStatsManager = new 
BrokerStatsManager(brokerConfig);
         MessageStore messageStore;
         if (isRocksDBStore) {
-            messageStore = new RocksDBMessageStore(storeConfig, 
brokerStatsManager, null, brokerConfig, topicConfigTable);
+            messageStore = new RocksDBMessageStore(storeConfig, 
brokerStatsManager, messageArrivingListener, brokerConfig, topicConfigTable);
         } else {
-            messageStore = new DefaultMessageStore(storeConfig, 
brokerStatsManager, null, brokerConfig, topicConfigTable);
+            messageStore = new DefaultMessageStore(storeConfig, 
brokerStatsManager, messageArrivingListener, brokerConfig, topicConfigTable);
         }
         return messageStore;
     }
 
     public static MessageStore buildMessageStore(String storePathRootDir, 
final BrokerConfig brokerConfig,
         final ConcurrentMap<String, TopicConfig> topicConfigTable, boolean 
isRocksDBStore) throws Exception {
+        return buildMessageStore(storePathRootDir, brokerConfig, 
topicConfigTable, isRocksDBStore, null);
+    }
+
+    public static MessageStore buildMessageStore(String storePathRootDir, 
final BrokerConfig brokerConfig,
+        final ConcurrentMap<String, TopicConfig> topicConfigTable, boolean 
isRocksDBStore,
+        MessageArrivingListener messageArrivingListener) throws Exception {
         MessageStoreConfig storeConfig = new MessageStoreConfig();
         storeConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10);
         storeConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10);
@@ -65,7 +78,7 @@ public class LiteTestUtil {
         storeConfig.setEnableMultiDispatch(true);
         storeConfig.setStorePathRootDir(storePathRootDir);
 
-        return buildMessageStore(brokerConfig, storeConfig, topicConfigTable, 
isRocksDBStore);
+        return buildMessageStore(brokerConfig, storeConfig, topicConfigTable, 
isRocksDBStore, messageArrivingListener);
     }
 
     public static MessageExtBrokerInner buildMessage(String parentTopic, 
String liteTopic) {
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManagerTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManagerTest.java
index dd34840535..bbc1f4d431 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManagerTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/lite/RocksDBLiteLifecycleManagerTest.java
@@ -18,12 +18,20 @@
 package org.apache.rocketmq.broker.lite;
 
 import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.longpolling.NotifyMessageArrivingListener;
+import org.apache.rocketmq.broker.longpolling.PullRequestHoldService;
+import org.apache.rocketmq.broker.offset.ConsumerOffsetManager;
+import org.apache.rocketmq.broker.processor.NotificationProcessor;
+import org.apache.rocketmq.broker.processor.PopMessageProcessor;
 import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager;
 import org.apache.rocketmq.broker.topic.TopicConfigManager;
 import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicAttributes;
 import org.apache.rocketmq.common.TopicConfig;
 import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.attribute.TopicMessageType;
 import org.apache.rocketmq.common.lite.LiteUtil;
+import org.apache.rocketmq.store.MessageArrivingListener;
 import org.apache.rocketmq.store.MessageStore;
 import org.apache.rocketmq.store.config.MessageStoreConfig;
 import org.apache.rocketmq.store.plugin.AbstractPluginMessageStore;
@@ -40,6 +48,7 @@ import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
@@ -53,9 +62,10 @@ import static org.awaitility.Awaitility.await;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.when;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(MockitoJUnitRunner.Silent.class)
 public class RocksDBLiteLifecycleManagerTest {
 
     private final static BrokerConfig BROKER_CONFIG = new BrokerConfig();
@@ -63,30 +73,51 @@ public class RocksDBLiteLifecycleManagerTest {
     private static String storePathRootDir;
     private static MessageStore messageStore;
     private static RocksDBLiteLifecycleManager liteLifecycleManager;
+    private static LiteEventDispatcher liteEventDispatcher;
+    private static TopicConfig mockTopicConfig = new TopicConfig();
 
     @BeforeClass
     public static void setUp() throws Exception {
         storePathRootDir = System.getProperty("java.io.tmpdir") + 
File.separator + "store-rocksDBLifecycleTest";
         UtilAll.deleteFile(new File(storePathRootDir));
 
-        messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, 
BROKER_CONFIG, TOPIC_CONFIG_TABLE, true);
-        messageStore.load();
-        messageStore.start();
-
         BrokerController brokerController = 
Mockito.mock(BrokerController.class);
         LiteSharding liteSharding = Mockito.mock(LiteSharding.class);
         TopicConfigManager topicConfigManager = 
Mockito.mock(TopicConfigManager.class);
         SubscriptionGroupManager subscriptionGroupManager = 
Mockito.mock(SubscriptionGroupManager.class);
+        LiteSubscriptionRegistry liteSubscriptionRegistry = 
Mockito.mock(LiteSubscriptionRegistry.class);
+        ConsumerOffsetManager consumerOffsetManager = 
Mockito.mock(ConsumerOffsetManager.class);
+        when(consumerOffsetManager.getOffsetTable()).thenReturn(new 
ConcurrentHashMap<>());
+        when(consumerOffsetManager.getPullOffsetTable()).thenReturn(new 
ConcurrentHashMap<>());
+        // dispatch() fans out to subscribers right after maintaining the 
prefix index; no subscriber here
+        
when(liteSubscriptionRegistry.getAllSubscribers(nullable(String.class), 
anyString()))
+            .thenReturn(Collections.emptyMap());
 
         when(brokerController.getBrokerConfig()).thenReturn(BROKER_CONFIG);
-        when(brokerController.getMessageStore()).thenReturn(messageStore);
         
when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager);
         
when(brokerController.getSubscriptionGroupManager()).thenReturn(subscriptionGroupManager);
+        
when(brokerController.getLiteSubscriptionRegistry()).thenReturn(liteSubscriptionRegistry);
+        
when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager);
         
when(topicConfigManager.getTopicConfigTable()).thenReturn(TOPIC_CONFIG_TABLE);
+        
when(topicConfigManager.selectTopicConfig(anyString())).thenReturn(mockTopicConfig);
         
when(subscriptionGroupManager.getSubscriptionGroupTable()).thenReturn(new 
ConcurrentHashMap<>());
 
         RocksDBLiteLifecycleManager testObject = new 
RocksDBLiteLifecycleManager(brokerController, liteSharding);
         liteLifecycleManager = Mockito.spy(testObject);
+
+        // Wire the real notify path so putMessage drives the prefix index. 
RocksDB CQ is committed by
+        // RocksGroupCommitService (isNotifyMessageArriveWhenReput()==false), 
but it still lands on the same
+        // MessageArrivingListener -> LiteEventDispatcher.dispatch -> 
onLmqCreate path.
+        liteEventDispatcher = new LiteEventDispatcher(brokerController, 
liteSubscriptionRegistry, liteLifecycleManager);
+        MessageArrivingListener listener = new NotifyMessageArrivingListener(
+            Mockito.mock(PullRequestHoldService.class), 
Mockito.mock(PopMessageProcessor.class),
+            Mockito.mock(NotificationProcessor.class), liteEventDispatcher);
+
+        messageStore = LiteTestUtil.buildMessageStore(storePathRootDir, 
BROKER_CONFIG, TOPIC_CONFIG_TABLE, true, listener);
+        when(brokerController.getMessageStore()).thenReturn(messageStore);
+        messageStore.load();
+        messageStore.start();
+
         liteLifecycleManager.init();
     }
 
@@ -95,6 +126,7 @@ public class RocksDBLiteLifecycleManagerTest {
         messageStore.shutdown();
         messageStore.destroy();
         UtilAll.deleteFile(new File(storePathRootDir));
+        mockTopicConfig = new TopicConfig();
     }
 
     @Ignore
@@ -141,6 +173,69 @@ public class RocksDBLiteLifecycleManagerTest {
         Assert.assertEquals(0, 
liteLifecycleManager.getMaxOffsetInQueue(UUID.randomUUID().toString()));
     }
 
+    @Test
+    public void testCollectByParentTopic() {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
UUID.randomUUID().toString()));
+            
messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), 
UUID.randomUUID().toString()));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+        List<String> result = 
liteLifecycleManager.collectByParentTopic(parentTopic);
+        Assert.assertEquals(num, result.size());
+        for (String lmqName : result) {
+            Assert.assertTrue(LiteUtil.belongsTo(lmqName, parentTopic));
+        }
+
+        result = 
liteLifecycleManager.collectByParentTopic(UUID.randomUUID().toString());
+        Assert.assertEquals(0, result.size());
+    }
+
+    @Test
+    public void testGetLiteTopicCount() {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        mockTopicConfig.getAttributes().put(
+            TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), 
TopicMessageType.LITE.getValue());
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
UUID.randomUUID().toString()));
+            
messageStore.putMessage(LiteTestUtil.buildMessage(UUID.randomUUID().toString(), 
UUID.randomUUID().toString()));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+
+        Assert.assertEquals(num, 
liteLifecycleManager.getLiteTopicCount(parentTopic));
+        Assert.assertEquals(0, 
liteLifecycleManager.getLiteTopicCount(UUID.randomUUID().toString()));
+    }
+
+    @Test
+    public void testCleanByParentTopic() throws Exception {
+        int num = 3;
+        String parentTopic = UUID.randomUUID().toString();
+        mockTopicConfig.getAttributes().put(
+            TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName(), 
TopicMessageType.LITE.getValue());
+        List<String> liteTopics =
+            IntStream.range(0, num).mapToObj(i -> 
UUID.randomUUID().toString()).collect(Collectors.toList());
+        for (int i = 0; i < num; i++) {
+            messageStore.putMessage(LiteTestUtil.buildMessage(parentTopic, 
liteTopics.get(i)));
+        }
+        await().atMost(5, SECONDS).pollInterval(200, MILLISECONDS).until(() -> 
messageStore.dispatchBehindBytes() <= 0);
+
+        for (int i = 0; i < num; i++) {
+            String lmqName = LiteUtil.toLmqName(parentTopic, 
liteTopics.get(i));
+            Assert.assertEquals(1, (long) 
messageStore.getQueueStore().getMaxOffset(lmqName, 0));
+            Assert.assertEquals(1, 
liteLifecycleManager.getMaxOffsetInQueue(lmqName));
+        }
+
+        liteLifecycleManager.cleanByParentTopic(parentTopic);
+
+        for (int i = 0; i < num; i++) {
+            String lmqName = LiteUtil.toLmqName(parentTopic, 
liteTopics.get(i));
+            Assert.assertEquals(0, (long) 
messageStore.getQueueStore().getMaxOffset(lmqName, 0));
+            Assert.assertEquals(0, 
liteLifecycleManager.getMaxOffsetInQueue(lmqName));
+        }
+    }
+
     @Test
     public void testCleanExpiredLiteTopic() throws Exception {
         int num = 3;

Reply via email to