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

yuz10 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 37af1437e6 [ISSUE #10478] Support Lite messages in proxy local mode 
(#11053)
37af1437e6 is described below

commit 37af1437e60e48009105c4a32851fb67f4c7acb8
Author: Rui Yin <[email protected]>
AuthorDate: Sat Oct 3 15:27:10 2026 +0800

    [ISSUE #10478] Support Lite messages in proxy local mode (#11053)
    
    * [ISSUE #10478] Support Lite messages in proxy local mode
    
    * [ISSUE #10478] Cover Lite pop edge cases and log invalid metadata
---
 .../proxy/service/LocalServiceManager.java         |  13 +-
 .../proxy/service/message/LocalMessageService.java |  66 +++++++-
 .../service/message/LocalMessageServiceTest.java   | 171 +++++++++++++++++++++
 3 files changed, 248 insertions(+), 2 deletions(-)

diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
index 6e7827c4eb..111ce0fd52 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
@@ -55,8 +55,10 @@ public class LocalServiceManager extends 
AbstractStartAndShutdown implements Ser
     private final ProxyRelayService proxyRelayService;
     private final MetadataService metadataService;
     private final AdminService adminService;
+    private final LiteSubscriptionService liteSubscriptionService;
 
     private final MQClientAPIFactory mqClientAPIFactory;
+    private final MQClientAPIFactory liteSubscriptionAPIFactory;
     private final ChannelManager channelManager;
 
     private final ScheduledExecutorService scheduledExecutorService = 
ThreadUtils.newSingleThreadScheduledExecutor(
@@ -82,11 +84,20 @@ public class LocalServiceManager extends 
AbstractStartAndShutdown implements Ser
         this.proxyRelayService = new LocalProxyRelayService(brokerController, 
this.transactionService);
         this.metadataService = new LocalMetadataService(brokerController);
         this.adminService = new DefaultAdminService(this.mqClientAPIFactory);
+        this.liteSubscriptionAPIFactory = new MQClientAPIFactory(
+            nameserverAccessConfig,
+            "LiteSubscription_",
+            1,
+            new DoNothingClientRemotingProcessor(null),
+            rpcHook,
+            scheduledExecutorService);
+        this.liteSubscriptionService = new 
LiteSubscriptionService(this.topicRouteService, 
this.liteSubscriptionAPIFactory);
         this.init();
     }
 
     protected void init() {
         this.appendStartAndShutdown(this.mqClientAPIFactory);
+        this.appendStartAndShutdown(this.liteSubscriptionAPIFactory);
         this.appendStartAndShutdown(this.topicRouteService);
         this.appendStartAndShutdown(this.adminService);
         this.appendStartAndShutdown(new LocalServiceManagerStartAndShutdown());
@@ -134,7 +145,7 @@ public class LocalServiceManager extends 
AbstractStartAndShutdown implements Ser
 
     @Override
     public LiteSubscriptionService getLiteSubscriptionService() {
-        return null;
+        return liteSubscriptionService;
     }
 
     private class LocalServiceManagerStartAndShutdown implements 
StartAndShutdown {
diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
index c93fa93983..a14b09665e 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.broker.BrokerController;
 import org.apache.rocketmq.client.consumer.AckResult;
 import org.apache.rocketmq.client.consumer.AckStatus;
@@ -36,6 +37,7 @@ import org.apache.rocketmq.client.consumer.PullResult;
 import org.apache.rocketmq.client.producer.SendResult;
 import org.apache.rocketmq.client.producer.SendStatus;
 import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.common.consumer.ReceiptHandle;
 import org.apache.rocketmq.common.message.Message;
 import org.apache.rocketmq.common.message.MessageBatch;
@@ -68,6 +70,7 @@ import 
org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil;
 import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader;
 import 
org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.PopLiteMessageResponseHeader;
 import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader;
 import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader;
@@ -200,7 +203,68 @@ public class LocalMessageService implements MessageService 
{
     @Override
     public CompletableFuture<PopResult> popLiteMessage(ProxyContext ctx, 
AddressableMessageQueue messageQueue,
         PopLiteMessageRequestHeader requestHeader, long timeoutMillis) {
-        throw new NotImplementedException();
+        RemotingCommand request = 
LocalRemotingCommand.createRequestCommand(RequestCode.POP_LITE_MESSAGE, 
requestHeader, ctx.getLanguage());
+        CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
+        SimpleChannel channel = channelManager.createInvocationChannel(ctx);
+        InvocationContext invocationContext = new InvocationContext(future);
+        channel.registerInvocationContext(request.getOpaque(), 
invocationContext);
+        ChannelHandlerContext channelHandlerContext = 
channel.getChannelHandlerContext();
+        try {
+            RemotingCommand response = 
brokerController.getPopLiteMessageProcessor().processRequest(channelHandlerContext,
 request);
+            if (response != null) {
+                invocationContext.handle(response);
+                channel.eraseInvocationContext(request.getOpaque());
+            }
+        } catch (Exception e) {
+            future.completeExceptionally(e);
+            channel.eraseInvocationContext(request.getOpaque());
+            log.error("Failed to process popLiteMessage command", e);
+        }
+        return future.thenApply(r -> {
+            PopStatus popStatus;
+            List<MessageExt> messageExtList = new ArrayList<>();
+            switch (r.getCode()) {
+                case ResponseCode.SUCCESS:
+                    popStatus = PopStatus.FOUND;
+                    messageExtList = 
MessageDecoder.decodesBatch(ByteBuffer.wrap(r.getBody()), true, false, true);
+                    break;
+                case ResponseCode.POLLING_FULL:
+                    popStatus = PopStatus.POLLING_FULL;
+                    break;
+                case ResponseCode.POLLING_TIMEOUT:
+                case ResponseCode.PULL_NOT_FOUND:
+                    popStatus = PopStatus.POLLING_NOT_FOUND;
+                    break;
+                default:
+                    throw new 
ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, r.getRemark());
+            }
+            PopResult popResult = new PopResult(popStatus, messageExtList);
+            if (popStatus != PopStatus.FOUND) {
+                return popResult;
+            }
+            PopLiteMessageResponseHeader responseHeader = 
(PopLiteMessageResponseHeader) r.readCustomHeader();
+            List<Integer> orderCountList = 
ExtraInfoUtil.parseLiteOrderCountInfo(responseHeader.getOrderCountInfo(), 
messageExtList.size());
+            for (int i = 0; i < messageExtList.size(); i++) {
+                MessageExt messageExt = messageExtList.get(i);
+                String[] queues = 
StringUtils.split(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH),
 MixAll.LMQ_DISPATCH_SEPARATOR);
+                String[] queueOffsets = 
StringUtils.split(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET),
 MixAll.LMQ_DISPATCH_SEPARATOR);
+                if (queues == null || queueOffsets == null || queues.length != 
1 || queues.length != queueOffsets.length) {
+                    log.warn("Invalid Lite dispatch metadata, messageId={}, 
brokerName={}",
+                        messageExt.getMsgId(), messageQueue.getBrokerName());
+                    continue;
+                }
+                messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK,
+                    ExtraInfoUtil.buildExtraInfo(0, 
responseHeader.getPopTime(), responseHeader.getInvisibleTime(),
+                        responseHeader.getReviveQid(), 
messageQueue.getTopic(), messageQueue.getBrokerName(), 0,
+                        Long.parseLong(queueOffsets[0])));
+                
messageExt.getProperties().computeIfAbsent(MessageConst.PROPERTY_FIRST_POP_TIME,
+                    k -> String.valueOf(responseHeader.getPopTime()));
+                messageExt.setBrokerName(messageQueue.getBrokerName());
+                messageExt.setReconsumeTimes(orderCountList != null ? 
orderCountList.get(i) : 0);
+                messageExt.setQueueOffset(Long.parseLong(queueOffsets[0]));
+            }
+            return popResult;
+        });
     }
 
     @Override
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java
index 52ba521f80..a0fe3b9c98 100644
--- 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/message/LocalMessageServiceTest.java
@@ -28,10 +28,12 @@ import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import org.apache.rocketmq.broker.BrokerController;
 import org.apache.rocketmq.broker.processor.AckMessageProcessor;
 import org.apache.rocketmq.broker.processor.ChangeInvisibleTimeProcessor;
 import org.apache.rocketmq.broker.processor.EndTransactionProcessor;
+import org.apache.rocketmq.broker.processor.PopLiteMessageProcessor;
 import org.apache.rocketmq.broker.processor.PopMessageProcessor;
 import org.apache.rocketmq.broker.processor.RecallMessageProcessor;
 import org.apache.rocketmq.broker.processor.SendMessageProcessor;
@@ -46,6 +48,7 @@ import org.apache.rocketmq.common.consumer.ReceiptHandle;
 import org.apache.rocketmq.common.message.Message;
 import org.apache.rocketmq.common.message.MessageBatch;
 import org.apache.rocketmq.common.message.MessageClientIDSetter;
+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.MessageQueue;
@@ -70,6 +73,8 @@ import 
org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil;
 import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.PopMessageResponseHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.PopLiteMessageRequestHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.PopLiteMessageResponseHeader;
 import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader;
 import 
org.apache.rocketmq.remoting.protocol.header.RecallMessageResponseHeader;
 import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
@@ -95,6 +100,8 @@ public class LocalMessageServiceTest extends InitConfigTest {
     @Mock
     private PopMessageProcessor popMessageProcessorMock;
     @Mock
+    private PopLiteMessageProcessor popLiteMessageProcessorMock;
+    @Mock
     private ChangeInvisibleTimeProcessor changeInvisibleTimeProcessorMock;
     @Mock
     private AckMessageProcessor ackMessageProcessorMock;
@@ -126,6 +133,7 @@ public class LocalMessageServiceTest extends InitConfigTest 
{
         channelManager = new ChannelManager();
         
Mockito.when(brokerControllerMock.getSendMessageProcessor()).thenReturn(sendMessageProcessorMock);
         
Mockito.when(brokerControllerMock.getPopMessageProcessor()).thenReturn(popMessageProcessorMock);
+        
Mockito.when(brokerControllerMock.getPopLiteMessageProcessor()).thenReturn(popLiteMessageProcessorMock);
         
Mockito.when(brokerControllerMock.getChangeInvisibleTimeProcessor()).thenReturn(changeInvisibleTimeProcessorMock);
         
Mockito.when(brokerControllerMock.getAckMessageProcessor()).thenReturn(ackMessageProcessorMock);
         
Mockito.when(brokerControllerMock.getEndTransactionProcessor()).thenReturn(endTransactionProcessorMock);
@@ -277,6 +285,169 @@ public class LocalMessageServiceTest extends 
InitConfigTest {
         }));
     }
 
+    @Test
+    public void testPopLiteMessageWriteAndFlush() throws Exception {
+        long popTime = System.currentTimeMillis();
+        long invisibleTime = 3000L;
+        List<MessageExt> messages = new ArrayList<>();
+        MessageExt message1 = buildMessageExt(topic, 0, 100L);
+        
message1.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, 
"%LMQ%$topic$lite");
+        
message1.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, 
"0");
+        messages.add(message1);
+        MessageExt message2 = buildMessageExt(topic, 0, 101L);
+        
message2.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, 
"%LMQ%$topic$lite");
+        
message2.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, 
"1");
+        messages.add(message2);
+        byte[] body = ByteBuffer.allocate(MessageDecoder.encode(message1, 
false).length + MessageDecoder.encode(message2, false).length)
+            .put(MessageDecoder.encode(message1, 
false)).put(MessageDecoder.encode(message2, false)).array();
+        PopLiteMessageRequestHeader requestHeader = new 
PopLiteMessageRequestHeader();
+        requestHeader.setInvisibleTime(invisibleTime);
+        
Mockito.when(popLiteMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class),
 Mockito.argThat(argument ->
+            argument.getCode() == RequestCode.POP_LITE_MESSAGE
+                && argument.readCustomHeader() instanceof 
PopLiteMessageRequestHeader))).thenAnswer(invocation -> {
+                    SimpleChannelHandlerContext channelHandlerContext = 
invocation.getArgument(0);
+                    RemotingCommand request = invocation.getArgument(1);
+                    RemotingCommand response = 
RemotingCommand.createResponseCommand(PopLiteMessageResponseHeader.class);
+                    response.setOpaque(request.getOpaque());
+                    response.setCode(ResponseCode.SUCCESS);
+                    response.setBody(body);
+                    PopLiteMessageResponseHeader responseHeader = 
(PopLiteMessageResponseHeader) response.readCustomHeader();
+                    responseHeader.setPopTime(popTime);
+                    responseHeader.setInvisibleTime(invisibleTime);
+                    responseHeader.setReviveQid(1);
+                    channelHandlerContext.writeAndFlush(response);
+                    return null;
+                });
+
+        PopResult result = localMessageService.popLiteMessage(proxyContext,
+            new AddressableMessageQueue(new MessageQueue(topic, brokerName, 
queueId), ""), requestHeader, 1000L).get();
+        assertThat(result.getPopStatus()).isEqualTo(PopStatus.FOUND);
+        assertThat(result.getMsgFoundList()).hasSize(2);
+        
assertThat(result.getMsgFoundList().get(0).getQueueOffset()).isEqualTo(0L);
+        
assertThat(result.getMsgFoundList().get(1).getQueueOffset()).isEqualTo(1L);
+        
assertThat(result.getMsgFoundList().get(0).getBrokerName()).isEqualTo(brokerName);
+    }
+
+    @Test
+    public void testPopLiteMessagePollingResponses() throws Exception {
+        int[] codes = {ResponseCode.POLLING_FULL, 
ResponseCode.POLLING_TIMEOUT, ResponseCode.PULL_NOT_FOUND};
+        PopStatus[] statuses = {PopStatus.POLLING_FULL, 
PopStatus.POLLING_NOT_FOUND, PopStatus.POLLING_NOT_FOUND};
+        for (int i = 0; i < codes.length; i++) {
+            PopResult result = 
invokeLiteResponse(RemotingCommand.createResponseCommand(codes[i], ""));
+            assertThat(result.getPopStatus()).isEqualTo(statuses[i]);
+            assertThat(result.getMsgFoundList()).isEmpty();
+        }
+    }
+
+    @Test
+    public void testPopLiteMessageEmptyBatch() throws Exception {
+        PopResult result = invokeLiteResponse(buildLiteResponse(null));
+        assertThat(result.getPopStatus()).isEqualTo(PopStatus.FOUND);
+        assertThat(result.getMsgFoundList()).isEmpty();
+    }
+
+    @Test
+    public void testPopLiteMessageMalformedDispatchProperties() throws 
Exception {
+        String[][] properties = {
+            {null, "7"}, {"%LMQ%$topic$lite", null},
+            {"%LMQ%$topic$lite,%LMQ%$topic$other", "7,8"}, 
{"%LMQ%$topic$lite", "7,8"}
+        };
+        for (String[] property : properties) {
+            MessageExt message = buildMessageExt(topic, 0, 100L);
+            if (property[0] != null) {
+                
message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, 
property[0]);
+            }
+            if (property[1] != null) {
+                
message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, 
property[1]);
+            }
+            PopResult result = invokeLiteResponse(buildLiteResponse(null, 
message));
+            assertThat(result.getMsgFoundList()).hasSize(1);
+            
assertThat(result.getMsgFoundList().get(0).getQueueOffset()).isEqualTo(100L);
+            
assertThat(result.getMsgFoundList().get(0).getProperty(MessageConst.PROPERTY_POP_CK)).isNull();
+        }
+    }
+
+    @Test
+    public void testPopLiteMessageMissingOrMismatchedOrderCounts() throws 
Exception {
+        for (String counts : new String[] {null, "", "3", "3;4;5"}) {
+            PopResult result = invokeLiteResponse(buildLiteResponse(counts, 
buildLiteMessage(7L), buildLiteMessage(8L)));
+            assertThat(result.getMsgFoundList()).hasSize(2);
+            for (MessageExt message : result.getMsgFoundList()) {
+                assertThat(message.getReconsumeTimes()).isZero();
+                
assertThat(message.getProperty(MessageConst.PROPERTY_POP_CK)).isNotNull();
+            }
+        }
+    }
+
+    @Test
+    public void testPopLiteMessageOrderCountsAndReceiptMetadata() throws 
Exception {
+        PopResult result = invokeLiteResponse(buildLiteResponse("3;4", 
buildLiteMessage(7L), buildLiteMessage(8L)));
+        for (int i = 0; i < 2; i++) {
+            MessageExt message = result.getMsgFoundList().get(i);
+            assertThat(message.getReconsumeTimes()).isEqualTo(i + 3);
+            assertThat(message.getQueueOffset()).isEqualTo(i + 7L);
+            assertThat(message.getBrokerName()).isEqualTo(brokerName);
+            
assertThat(message.getProperty(MessageConst.PROPERTY_FIRST_POP_TIME)).isEqualTo("123456");
+            
assertThat(message.getProperty(MessageConst.PROPERTY_POP_CK)).isEqualTo(
+                ExtraInfoUtil.buildExtraInfo(0, 123456L, 3000L, 1, topic, 
brokerName, 0, i + 7L));
+        }
+    }
+
+    @Test
+    public void testPopLiteMessageBrokerError() throws Exception {
+        ExecutionException exception = catchThrowableOfType(() -> 
invokeLiteResponse(
+            RemotingCommand.createResponseCommand(ResponseCode.SYSTEM_ERROR, 
"lite failure")), ExecutionException.class);
+        assertThat(exception.getCause()).isInstanceOf(ProxyException.class);
+        assertThat(((ProxyException) 
exception.getCause()).getCode()).isEqualTo(ProxyExceptionCode.INTERNAL_SERVER_ERROR);
+        assertThat(exception.getCause()).hasMessage("lite failure");
+    }
+
+    @Test
+    public void testPopLiteMessageProcessorException() throws Exception {
+        RemotingCommandException failure = new RemotingCommandException("lite 
failure");
+        
Mockito.when(popLiteMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class),
 Mockito.any()))
+            .thenThrow(failure);
+        ExecutionException exception = catchThrowableOfType(() -> 
localMessageService.popLiteMessage(proxyContext,
+            null, new PopLiteMessageRequestHeader(), 1000L).get(5, 
TimeUnit.SECONDS), ExecutionException.class);
+        assertThat(exception.getCause()).isSameAs(failure);
+    }
+
+    private MessageExt buildLiteMessage(long offset) {
+        MessageExt message = buildMessageExt(topic, 0, 100L);
+        
message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_DISPATCH, 
"%LMQ%$topic$lite");
+        
message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, 
Long.toString(offset));
+        return message;
+    }
+
+    private RemotingCommand buildLiteResponse(String orderCounts, 
MessageExt... messages) throws Exception {
+        List<byte[]> encoded = new ArrayList<>();
+        int size = 0;
+        for (MessageExt message : messages) {
+            byte[] bytes = MessageDecoder.encode(message, false);
+            encoded.add(bytes);
+            size += bytes.length;
+        }
+        ByteBuffer body = ByteBuffer.allocate(size);
+        encoded.forEach(body::put);
+        RemotingCommand response = 
RemotingCommand.createResponseCommand(PopLiteMessageResponseHeader.class);
+        response.setCode(ResponseCode.SUCCESS);
+        response.setBody(body.array());
+        PopLiteMessageResponseHeader header = (PopLiteMessageResponseHeader) 
response.readCustomHeader();
+        header.setPopTime(123456L);
+        header.setInvisibleTime(3000L);
+        header.setReviveQid(1);
+        header.setOrderCountInfo(orderCounts);
+        return response;
+    }
+
+    private PopResult invokeLiteResponse(RemotingCommand response) throws 
Exception {
+        
Mockito.when(popLiteMessageProcessorMock.processRequest(Mockito.any(SimpleChannelHandlerContext.class),
 Mockito.any()))
+            .thenReturn(response);
+        return localMessageService.popLiteMessage(proxyContext,
+            new AddressableMessageQueue(new MessageQueue(topic, brokerName, 
queueId), ""),
+            new PopLiteMessageRequestHeader(), 1000L).get(5, TimeUnit.SECONDS);
+    }
+
     @Test
     public void testPopMessageWriteAndFlush() throws Exception {
         int reviveQueueId = 1;

Reply via email to