From 7d2c4a3bb7ce72d877a37305e71fabb65388732b Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 14 Oct 2025 19:29:46 +0800 Subject: [PATCH 001/101] docs: add comments to class GrpcMessagingApplication --- .../grpc/v2/GrpcMessagingApplication.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java index 9ee3f4fddd4..7446dc5378d 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java @@ -71,6 +71,15 @@ import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter; import org.apache.rocketmq.proxy.processor.MessagingProcessor; +/** + * RocketMQ gRPC protocol implementation + * + * + */ public class GrpcMessagingApplication extends MessagingServiceGrpc.MessagingServiceImplBase implements StartAndShutdown { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); @@ -167,6 +176,16 @@ protected Status convertExceptionToStatus(Throwable t) { return ResponseBuilder.getInstance().buildStatus(t); } + /** + * submit grpc task to related thread pool. + * + * @param executor thread pool + * @param context context + * @param request grpc request + * @param runnable process task + * @param responseObserver grpc response observer + * @param statusResponseCreator error response creator + */ protected void addExecutor(ExecutorService executor, ProxyContext context, V request, Runnable runnable, StreamObserver responseObserver, Function statusResponseCreator) { if (request instanceof GeneratedMessageV3) { @@ -200,6 +219,12 @@ protected void validateContext(ProxyContext context) { } } + /** + * route query api, producer/consumer will call this api while starting. + * + * @param request request + * @param responseObserver gRPC response observer + */ @Override public void queryRoute(QueryRouteRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> QueryRouteResponse.newBuilder().setStatus(status).build(); @@ -217,6 +242,12 @@ public void queryRoute(QueryRouteRequest request, StreamObserver responseObserver) { Function statusResponseCreator = status -> HeartbeatResponse.newBuilder().setStatus(status).build(); @@ -251,6 +282,12 @@ public void sendMessage(SendMessageRequest request, StreamObserver responseObserver) { @@ -399,6 +436,15 @@ public void recallMessage(RecallMessageRequest request, StreamObserver + *
  • register producer/consumer
  • + *
  • process trace
  • + *
  • verify message result
  • + * + */ @Override public StreamObserver telemetry(StreamObserver responseObserver) { Function statusResponseCreator = status -> TelemetryCommand.newBuilder().setStatus(status).build(); From 512bc80c1d2847997fc8010c8cd1cb7a88f68986 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Fri, 14 Nov 2025 08:20:00 +0800 Subject: [PATCH 002/101] comment: add comments to ProxyStartup --- .../java/org/apache/rocketmq/proxy/ProxyStartup.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java index 131faffa38e..c34c3f4e5d3 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java @@ -55,6 +55,14 @@ public class ProxyStartup { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + /** + * proxy components container, manager components with method start/shutdown/... + * - gRPC thread pool executor + * - message processor (wrap broker controller) + * - grpc server + * - remoting protocol server + * - ... + */ private static final ProxyStartAndShutdown PROXY_START_AND_SHUTDOWN = new ProxyStartAndShutdown(); private static class ProxyStartAndShutdown extends AbstractStartAndShutdown { @@ -73,8 +81,10 @@ public static void main(String[] args) { // init thread pool monitor for proxy. initThreadPoolMonitor(); + // init business thread pool for grpc server ThreadPoolExecutor executor = createServerExecutor(); + // create message processor, wrap broker controller in local mode MessagingProcessor messagingProcessor = createMessagingProcessor(); // tls cert update From a0cac8589251fb0bd3fdf5b327c50e40119251aa Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 16 Nov 2025 21:31:01 +0800 Subject: [PATCH 003/101] comment: add comments to remoting related thread pool config of ProxyConfig --- .../rocketmq/proxy/config/ProxyConfig.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java index a99b0afc352..db480ee850b 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -243,7 +243,7 @@ public class ProxyConfig implements ConfigFile { private String remotingAccessAddr = ""; private int remotingListenPort = 8080; - // related to proxy's send strategy in cluster mode. + // related to proxy's sending strategy in cluster mode. private boolean sendLatencyEnable = false; private boolean startDetectorEnable = false; private int detectTimeout = 200; @@ -251,9 +251,38 @@ public class ProxyConfig implements ConfigFile { private int remotingHeartbeatThreadPoolNums = 2 * PROCESSOR_NUMBER; private int remotingTopicRouteThreadPoolNums = 2 * PROCESSOR_NUMBER; + /** + * thread pool number for + * 1. send message(and send message v2) + * 2. send batch message + * 3. consume send message back + * 4. end transaction + * 5. recall message + */ private int remotingSendMessageThreadPoolNums = 4 * PROCESSOR_NUMBER; + /** + * thread pool number for + * 1. pull message + * 2. lite pull message + * 3. pop message + */ private int remotingPullMessageThreadPoolNums = 4 * PROCESSOR_NUMBER; + /** + * thread pool number for + * 1. update consumer offset + * 2. ack message + * 3. change message invisible time + * 4. get consumer connection list + */ private int remotingUpdateOffsetThreadPoolNums = 4 * PROCESSOR_NUMBER; + /** + * thread pool number for + * 1. unregister client + * 2. check client config + * 3. get consumer list by group + * 4. get min/max offset, query consume offset, search offset by timestamp + * 5. lock/unlock batch mq + */ private int remotingDefaultThreadPoolNums = 4 * PROCESSOR_NUMBER; private int remotingHeartbeatThreadPoolQueueCapacity = 50000; From 156ec2281ac56965a83a1a0f84d9d3277d4c981a Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 16 Nov 2025 21:48:33 +0800 Subject: [PATCH 004/101] comment: add comments to method sendMessage of SendMessageActivity of grpc --- .../grpc/v2/producer/SendMessageActivity.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java index f7b8014bb99..4ec2042404e 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java @@ -63,6 +63,20 @@ public SendMessageActivity(MessagingProcessor messagingProcessor, super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } + /** + * send message, execute in producer thread pool + * request flow: + * producer -> grpcRequest -> GrpcMessagingApplication -> ProducerThreadPoolForGrpc(...) + * functionality: + * 1. validate topic + * 2. create queue selector + * 3. build and validate message + * 4. convert response + * + * @param ctx proxy context + * @param request send message request + * @return send message response future + */ public CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request) { CompletableFuture future = new CompletableFuture<>(); From 7517165efc8eb3b0550f7b79b54c4cda9ebe0420 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 16 Nov 2025 22:20:01 +0800 Subject: [PATCH 005/101] comment: add comments to method sendMessage of ProducerProcessor --- .../rocketmq/proxy/processor/ProducerProcessor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java index 5aeb553f216..26553568e58 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java @@ -66,6 +66,14 @@ public ProducerProcessor(MessagingProcessor messagingProcessor, this.topicMessageTypeValidator = new DefaultTopicMessageTypeValidator(); } + /** + * send message + * 1. validate message type + * 2. select queue + * 3. set message id if not set + * 4. call message service + * 5. fill transaction data if send succeed and is transaction message + */ public CompletableFuture> sendMessage(ProxyContext ctx, QueueSelector queueSelector, String producerGroup, int sysFlag, List messageList, long timeoutMillis) { CompletableFuture> future = new CompletableFuture<>(); From d567caf16f34e71ed559993215766f5275b7d70e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 16 Nov 2025 22:27:04 +0800 Subject: [PATCH 006/101] comment: add comments to method sendMessage of MessageService --- .../org/apache/rocketmq/proxy/processor/ProducerProcessor.java | 1 + .../apache/rocketmq/proxy/service/message/MessageService.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java index 26553568e58..d92fbf66e85 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java @@ -104,6 +104,7 @@ public CompletableFuture> sendMessage(ProxyContext ctx, QueueSe SendMessageRequestHeader requestHeader = buildSendMessageRequestHeader(messageList, producerGroup, sysFlag, messageQueue.getQueueId()); AddressableMessageQueue finalMessageQueue = messageQueue; + // call SendMessageProcessor of broker future = this.serviceManager.getMessageService().sendMessage( ctx, messageQueue, diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java index 80f5ae7217c..6ad3e3ed884 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java @@ -46,6 +46,9 @@ public interface MessageService { + /** + * call SendMessageProcessor of broker + */ CompletableFuture> sendMessage( ProxyContext ctx, AddressableMessageQueue messageQueue, From 30aa8b545b3bed040688725f208427e6d170f1a8 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 6 May 2026 19:00:12 +0800 Subject: [PATCH 007/101] comment: add comments of ack mode to BrokerConfig.popConsumerKVServiceEnable --- .../java/org/apache/rocketmq/common/BrokerConfig.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java index 08e27a20ee3..a9892253bf0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -243,6 +243,11 @@ public class BrokerConfig extends BrokerIdentity { private int popFromRetryProbabilityForPriority = 0; // 0 as the lowest priority if true private boolean priorityOrderAsc = true; + /** + * There are two types of ack mode: + * 1. ack by file system service, which is the default mode. + * 2. ack by key-value service, when popConsumerKVServiceEnable and popConsumerKVServiceInit are both true. + */ private boolean popConsumerFSServiceInit = true; private boolean popConsumerKVServiceLog = false; private boolean popConsumerKVServiceInit = false; @@ -463,7 +468,7 @@ public class BrokerConfig extends BrokerIdentity { private boolean usePIDColdCtrStrategy = true; private long cgColdReadThreshold = 3 * 1024 * 1024; private long globalColdReadThreshold = 100 * 1024 * 1024; - + /** * The interval to fetch namesrv addr, default value is 10 second */ @@ -2104,11 +2109,11 @@ public boolean isUseStaticSubscription() { public void setUseStaticSubscription(boolean useStaticSubscription) { this.useStaticSubscription = useStaticSubscription; } - + public long getFetchNamesrvAddrInterval() { return fetchNamesrvAddrInterval; } - + public void setFetchNamesrvAddrInterval(final long fetchNamesrvAddrInterval) { this.fetchNamesrvAddrInterval = fetchNamesrvAddrInterval; } From a75c7bae5b739e4eeec127c2b1b74d033c27ec4e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 14 May 2026 14:17:02 +0800 Subject: [PATCH 008/101] comment: add class comments to SelectMappedBufferResult --- .../org/apache/rocketmq/store/SelectMappedBufferResult.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java b/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java index 5c38cfe92a9..b96dfd98882 100644 --- a/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/SelectMappedBufferResult.java @@ -19,6 +19,12 @@ import java.nio.ByteBuffer; import org.apache.rocketmq.store.logfile.MappedFile; +/** + * result while select mapped file + * - mapped file + * - offset and size + * - whether it is in memory + */ public class SelectMappedBufferResult { private final long startOffset; From 477cec21e160bfffdc72cdc4bc8330737ba5779c Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 14 May 2026 14:24:23 +0800 Subject: [PATCH 009/101] comment: add class comments to GetMessageResult --- .../org/apache/rocketmq/store/GetMessageResult.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java index 6f322a19e19..980f9a7bc89 100644 --- a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java @@ -21,15 +21,23 @@ import java.util.Collections; import java.util.List; +/** + * result of get message + */ public class GetMessageResult { - + // mappedFile info list private final List messageMapedList; + // message info list in form of ByteBuffer, used by zero copy in version 4.* private final List messageBufferList; + // consume queue offset list private final List messageQueueOffset; private GetMessageStatus status; + // next offset of queue(Consume Queue) private long nextBeginOffset; + // min offset of queue(Consume Queue) private long minOffset; + // max offset of queue(Consume Queue) private long maxOffset; private int bufferTotalSize = 0; From 3e81bb12e1bf5e643308a3405fef3a79a7b425b8 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 16 May 2026 11:49:45 +0800 Subject: [PATCH 010/101] comment: add comments to method getMessage --- .../rocketmq/store/DefaultMessageStore.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 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 aee767dae2f..cb8389111a0 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -900,19 +900,19 @@ public GetMessageResult getMessage(final String group, final String topic, final minOffset = consumeQueue.getMinOffsetInQueue(); maxOffset = consumeQueue.getMaxOffsetInQueue(); - if (maxOffset == 0) { + if (maxOffset == 0) { // empty queue status = GetMessageStatus.NO_MESSAGE_IN_QUEUE; nextBeginOffset = nextOffsetCorrection(offset, 0); - } else if (offset < minOffset) { + } else if (offset < minOffset) { // offset too small status = GetMessageStatus.OFFSET_TOO_SMALL; nextBeginOffset = nextOffsetCorrection(offset, minOffset); - } else if (offset == maxOffset) { + } else if (offset == maxOffset) { // offset overflow one status = GetMessageStatus.OFFSET_OVERFLOW_ONE; nextBeginOffset = nextOffsetCorrection(offset, offset); - } else if (offset > maxOffset) { + } else if (offset > maxOffset) { // offset too big status = GetMessageStatus.OFFSET_OVERFLOW_BADLY; nextBeginOffset = nextOffsetCorrection(offset, maxOffset); - } else { + } else { // offset is ok final int maxFilterMessageSize = Math.max(this.messageStoreConfig.getMaxFilterMessageSize(), maxMsgNums * consumeQueue.getUnitSize()); final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded(); @@ -925,6 +925,14 @@ public GetMessageResult getMessage(final String group, final String topic, final long maxPhyOffsetPulling = 0; int cqFileNum = 0; + /* + * bufferTotalSize is the total message size + * bufferTotalSize less than 0 means + * the while loop will break after getting more than one messages + * + * travelCqFileNumWhenGetMessage limits the max file nums to travel when get message + * default is 1 + */ while (getResult.getBufferTotalSize() <= 0 && nextBeginOffset < maxOffset && cqFileNum++ < this.messageStoreConfig.getTravelCqFileNumWhenGetMessage()) { From c869be86f415165a129933c0ad3250ea137f8e84 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 16 May 2026 16:12:09 +0800 Subject: [PATCH 011/101] comment: add comments to method receiveMessage of ReceiveMessageActivity --- .../proxy/grpc/v2/consumer/ReceiveMessageActivity.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java index f5e1c7b76f3..771c05b7573 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java @@ -59,6 +59,16 @@ public ReceiveMessageActivity(MessagingProcessor messagingProcessor, super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager); } + /** + * + * @param ctx ctx + * @param request + * request.invisible_duration => + * Required if client type is simple consumer. + * useless for PushConsumer + * + * @param responseObserver responseObserver + */ public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, StreamObserver responseObserver) { ReceiveMessageResponseStreamWriter writer = createWriter(ctx, responseObserver); From ae075b33cf57283f15749bad42e965e76e826aef Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 17 May 2026 14:15:22 +0800 Subject: [PATCH 012/101] comment: add comments to the fields startOffsetInfo, msgOffsetInfo, and orderCountInfo --- .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 3 +++ .../proxy/grpc/v2/consumer/ReceiveMessageActivity.java | 1 + 2 files changed, 4 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index c32e1b5ae23..4b4708d4d01 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -503,8 +503,11 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC this.brokerController.getBrokerConfig().getReviveQueueNum()); } + // properties of rocketmq 4.x, useless in 5.x StringBuilder startOffsetInfo = new StringBuilder(64); + // properties of rocketmq 4.x, useless in 5.x StringBuilder msgOffsetInfo = new StringBuilder(64); + // properties of rocketmq 4.x, useless in 5.x StringBuilder orderCountInfo = requestHeader.isOrder() ? new StringBuilder(64) : null; // Due to the design of the fields startOffsetInfo, msgOffsetInfo, and orderCountInfo, diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java index 771c05b7573..ed32e3d5e61 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivity.java @@ -74,6 +74,7 @@ public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request, ReceiveMessageResponseStreamWriter writer = createWriter(ctx, responseObserver); try { + // Settings were registered when client connected Settings settings = this.grpcClientSettingsManager.getClientSettings(ctx); final boolean isLite = ClientType.LITE_PUSH_CONSUMER.equals(settings.getClientType()); From 031348e91400767b957094238f43c0c14b9a8684 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sun, 17 May 2026 14:22:17 +0800 Subject: [PATCH 013/101] comment: add comments of ack mode to method processRequest of PopMessageProcessor --- .../rocketmq/broker/processor/PopMessageProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 4b4708d4d01..b049cfb7367 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -382,6 +382,9 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC ExpressionMessageFilter finalMessageFilter = messageFilter; SubscriptionData finalSubscriptionData = subscriptionData; + // There are two type of ack mode: + // 1. ack by KV service + // 2. ack by file merge service if (brokerConfig.isPopConsumerKVServiceEnable()) { CompletableFuture popAsyncFuture = brokerController.getPopConsumerService().popAsync( @@ -492,8 +495,9 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC return response; }).thenAccept(result -> NettyRemotingAbstract.writeResponse(channel, request, result, null, brokerController.getBrokerMetricsManager().getRemotingMetricsManager())); return null; - } + } // end of ack by kv service + // start of ack by file merge service int randomQ = random.nextInt(100); int reviveQid; if (requestHeader.isOrder()) { From 7e490e9b438b3df62e6c7a771914d4656f6eb2cf Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 15:13:59 +0800 Subject: [PATCH 014/101] comment: add process flow comments to method processRequest of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index b049cfb7367..8049b4dc381 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -226,9 +226,11 @@ public void notifyMessageArriving(final String topic, final int queueId, final S public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { + // init request and response final long beginTimeMills = this.brokerController.getMessageStore().now(); Channel channel = ctx.channel(); + RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); response.setOpaque(request.getOpaque()); @@ -240,6 +242,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC } final PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); + // validation // Pop mode only supports consumption in cluster load balancing mode brokerController.getConsumerManager().compensateBasicConsumerInfo( requestHeader.getConsumerGroup(), ConsumeType.CONSUME_POP, MessageModel.CLUSTERING); @@ -319,6 +322,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC return response; } + // init filter BrokerConfig brokerConfig = brokerController.getBrokerConfig(); SubscriptionData subscriptionData = null; ExpressionMessageFilter messageFilter = null; @@ -497,7 +501,8 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC return null; } // end of ack by kv service - // start of ack by file merge service + // start of ack by file merge service mode + // init pop parameters int randomQ = random.nextInt(100); int reviveQid; if (requestHeader.isOrder()) { @@ -530,6 +535,8 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC } randomQ = usePriorityMode ? 0 : randomQ; // reset randomQ long popTime = System.currentTimeMillis(); + + // pop message CompletableFuture getMessageFuture = CompletableFuture.completedFuture(0L); if (needRetry && !requestHeader.isOrder()) { if (needRetryV1) { @@ -542,6 +549,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } } + if (requestHeader.getQueueId() < 0) { // read all queue getMessageFuture = popMsgFromTopic(topicConfig, false, getMessageResult, requestHeader, reviveQid, channel, @@ -553,6 +561,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, finalMessageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo)); } + // if not full , fetch retry again if (!needRetry && getMessageResult.getMessageMapedList().size() < requestHeader.getMaxMsgNums() && !requestHeader.isOrder()) { if (needRetryV1) { @@ -622,7 +631,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC requestHeader.getTopic(), requestHeader.getQueueId(), (int) (this.brokerController.getMessageStore().now() - beginTimeMills)); finalResponse.setBody(r); - } else { + } else { // zero copy final GetMessageResult tmpGetMessageResult = getMessageResult; try { FileRegion fileRegion = From f491213b6c01055a7358c874476d0d97038fee53 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 15:33:57 +0800 Subject: [PATCH 015/101] comment: add class comments to PopLongPollingService --- .../longpolling/PopLongPollingService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java index c595178d193..134887f9ad4 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopLongPollingService.java @@ -52,6 +52,24 @@ import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_SUC; import static org.apache.rocketmq.broker.longpolling.PollingResult.POLLING_TIMEOUT; +/** + * Pop-mode long polling service that suspends Pop requests and wakes them up when new messages arrive. + *

    + * Core responsibilities: + *

      + *
    • Suspend Pop requests — when the broker has no messages to return immediately, registers requests + * into the {@code pollingMap} keyed by {@code topic@cid@queueId} and waits
    • + *
    • Wake up on new message arrival — {@link #notifyMessageArriving} is triggered by the message arriving + * listener; it fetches matching Pop requests from the pollingMap, applies Tag filtering, and re-submits + * them to the PopMessageProcessor to return results to the client
    • + *
    • Timeout scanning — the background thread periodically scans the waiting queues and wakes up + * timed-out requests with an empty result
    • + *
    • Retry topic bridging — {@link #notifyMessageArrivingFromRetry} translates a new message on the retry + * topic into a wake-up notification on the original topic
    • + *
    • Resource cleanup — periodically removes stale polling entries for deleted topics or + * offline consumer groups
    • + *
    + */ public class PopLongPollingService extends ServiceThread { private static final Logger POP_LOGGER = From 80f3e62e25e3318ab2f21dacbcf44a3231d92197 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 17:16:32 +0800 Subject: [PATCH 016/101] comment: add version related comments to method processRequest of PopMessageProcessor --- .../rocketmq/broker/processor/PopMessageProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 8049b4dc381..809343ddae6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -586,6 +586,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC POP_LOGGER.error("PopProcessor execute callback error", t); } + // long polling used in version 4.*, useless in 5.* if (!getMessageResult.getMessageBufferList().isEmpty()) { finalResponse.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); @@ -612,6 +613,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC } getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } + responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(reviveQid); @@ -622,6 +624,9 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC responseHeader.setOrderCountInfo(orderCountInfo.toString()); } finalResponse.setRemark(getMessageResult.getStatus().name()); + + // transfer msg by heap or zero copy, + // zero copy used in 4.*, useless in 5.* switch (finalResponse.getCode()) { case ResponseCode.SUCCESS: if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) { From 92bffeaecf2a181559bd3e32953bb9dcc65128f0 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 19:24:53 +0800 Subject: [PATCH 017/101] comment: add method comments to method popMsgFromTopic of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 809343ddae6..21f28812bb1 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -575,6 +575,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC } } + // async result handle final RemotingCommand finalResponse = response; getMessageFuture.thenApply(restNum -> { try { @@ -673,6 +674,37 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC return null; } + /** + * Pop messages from every read queue of the given topic. + * + *

    Queues are visited sequentially (respecting {@code priorityOrderAsc}). + * For each queue a {@link #popMsgFromQueue} call is chained via + * {@code CompletableFuture#thenCompose}. The chained future carries the + * remaining number of messages still needed ({@code restNum}). + * + *

    Early termination can occur inside {@link #popMsgFromQueue} when: + *

      + *
    • the queue lock cannot be acquired
    • + *
    • too many in-flight (un-acked) messages exist
    • + *
    • an order queue is blocked
    • + *
    • the accumulated message count already reaches {@code maxMsgNums}
    • + *
    + * + * @param topicConfig topic configuration; {@code null} skips all queues + * @param isRetry whether the topic is a retry topic + * @param getMessageResult accumulator for the messages popped so far + * @param requestHeader pop request parameters + * @param reviveQid revive queue id + * @param channel netty channel of the requesting client + * @param popTime pop timestamp + * @param messageFilter expression filter applied to each message + * @param startOffsetInfo buffer for offset tracing info + * @param msgOffsetInfo buffer for per-message offset tracing info + * @param orderCountInfo buffer for order-consume count info + * @param randomQ random queue offset for round-robin load balancing + * @param getMessageFuture future that carries the remaining message count + * @return a future completing with the remaining number of messages needed + */ private CompletableFuture popMsgFromTopic(TopicConfig topicConfig, boolean isRetry, GetMessageResult getMessageResult, PopMessageRequestHeader requestHeader, int reviveQid, Channel channel, long popTime, ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo, From c43e83e89a1b50b87da4ed7b57f9f2b15796f4fd Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 20:00:44 +0800 Subject: [PATCH 018/101] comment: add process flow comments to method popMsgFromQueue of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 21f28812bb1..171a1bfd66b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -738,6 +738,7 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, Channel channel, long popTime, ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo, StringBuilder msgOffsetInfo, StringBuilder orderCountInfo) { + // get pop offset String lockKey = topic + PopAckConstants.SPLIT + requestHeader.getConsumerGroup() + PopAckConstants.SPLIT + queueId; boolean isOrder = requestHeader.isOrder(); @@ -751,6 +752,7 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, return failure; } + // try lock CompletableFuture future = new CompletableFuture<>(); if (!queueLockManager.tryLock(lockKey)) { try { @@ -763,8 +765,9 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, } return future; } - future.whenComplete((result, throwable) -> queueLockManager.unLock(lockKey)); + + // check inflight message number if (isPopShouldStop(topic, requestHeader.getConsumerGroup(), queueId)) { POP_LOGGER.warn("Too much msgs unacked, then stop popping. topic={}, group={}, queueId={}", topic, requestHeader.getConsumerGroup(), queueId); @@ -777,6 +780,7 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, return future; } + // check orderly lock and max message number try { offset = getPopOffset(topic, requestHeader.getConsumerGroup(), queueId, requestHeader.getInitMode(), true, lockKey, true); @@ -817,6 +821,7 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, return this.brokerController.getMessageStore() .getMessageAsync(requestHeader.getConsumerGroup(), topic, queueId, offset, requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter) + // result check and retry if offset is not correct .thenCompose(result -> { if (result == null) { return CompletableFuture.completedFuture(null); @@ -837,7 +842,9 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter); } return CompletableFuture.completedFuture(result); - }).thenApply(result -> { + }) + // update order info or append checkpoint then format result + .thenApply(result -> { if (result == null) { try { atomicRestNum.set(brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - atomicOffset.get() + atomicRestNum.get()); @@ -929,7 +936,9 @@ private CompletableFuture popMsgFromQueue(String topic, String attemptId, result.getMessageCount() ); return atomicRestNum.get(); - }).whenComplete((result, throwable) -> { + }) + // unlock queueLock + .whenComplete((result, throwable) -> { if (throwable != null) { POP_LOGGER.error("Pop message error, {}", lockKey, throwable); } From af61749246c87f391570301408f8fdb3b41d14b2 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 20:09:41 +0800 Subject: [PATCH 019/101] comment: add method comments to method popMsgFromQueue of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 171a1bfd66b..269914580bb 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -732,6 +732,45 @@ private CompletableFuture popMsgFromTopic(String topic, boolean isRetry, G messageFilter, startOffsetInfo, msgOffsetInfo, orderCountInfo, randomQ, getMessageFuture); } + /** + * Pop messages from a specific queue of a topic. + * + *

    This method is called as a step in a {@link CompletableFuture} chain + * (see {@link #popMsgFromTopic}). The {@code restNum} argument is the + * number of messages still needed — when it drops to {@code 0} or below, + * subsequent calls in the chain may short-circuit early. + * + *

    The method has several early-termination paths (all return + * immediately with the current {@code restNum}): + *

      + *
    • Queue lock cannot be acquired — skips this queue
    • + *
    • Too many in-flight (un-acked) messages for this + * {@code topic@group@queueId}
    • + *
    • Order queue is blocked by a previous un-acked message
    • + *
    • Already accumulated {@code >= maxMsgNums} messages
    • + *
    + * + *

    Otherwise, it asynchronously fetches messages from the store, handles + * offset correction, updates order-consume tracking / checkpoint data, and + * merges the results into {@code getMessageResult}. + * + * @param topic topic name + * @param attemptId attempt id for idempotent consumption + * @param isRetry whether this is a retry topic + * @param getMessageResult accumulator for messages popped so far + * @param requestHeader pop request parameters + * @param queueId target queue id + * @param restNum number of messages still needed before the batch + * size is satisfied + * @param reviveQid revive queue id for checkpoint + * @param channel netty channel of the requesting client + * @param popTime pop invocation timestamp + * @param messageFilter expression filter applied to each message + * @param startOffsetInfo buffer for offset tracing info + * @param msgOffsetInfo buffer for per-message offset tracing info + * @param orderCountInfo buffer for order-consume count info + * @return a future completing with the remaining number of messages needed + */ private CompletableFuture popMsgFromQueue(String topic, String attemptId, boolean isRetry, GetMessageResult getMessageResult, PopMessageRequestHeader requestHeader, int queueId, long restNum, int reviveQid, From 314879f5bb407a6b93e24fc63508a48719a597e4 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 20:15:09 +0800 Subject: [PATCH 020/101] comment: add comments to method getPopOffset of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 269914580bb..1d760f23782 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -990,6 +990,25 @@ private boolean isPopShouldStop(String topic, String group, int queueId) { brokerController.getPopInflightMessageCounter().getGroupPopInFlightMessageNum(topic, group, queueId) > brokerController.getBrokerConfig().getPopInflightMessageThreshold(); } + /** + * get consume offset for pop mode + * called by: + * - this.popMsgFromQueue() + * functionality: + * - return resetOffset if exists + * - get offset if exists + * - init offset if not exists + * - get offset from popBufferMergeService + * + * @param topic topic + * @param group group + * @param queueId queueId + * @param initMode initMode ConsumeInitMode.MAX for pop mode + * @param init flag of whether commit offset the first time pop message + * @param lockKey lockKey + * @param checkResetOffset flag of whether resetPopOffset + * @return offset + */ private long getPopOffset(String topic, String group, int queueId, int initMode, boolean init, String lockKey, boolean checkResetOffset) throws ConsumeQueueException { From 174e0ff40e57d905e03a344fe642c4aa0856553e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 20:31:28 +0800 Subject: [PATCH 021/101] comment: add comments to method getInitOffset of PopMessageProcessor --- .../rocketmq/broker/processor/PopMessageProcessor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 1d760f23782..7bdde5339f7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -1032,6 +1032,14 @@ private long getPopOffset(String topic, String group, int queueId, int initMode, } } + /** + * get offset from consume queue + * If consume from min offset: + * - return min offset. + * If consume from max offset: + * - get max offset + * - commit max offset if init is true. + */ public long getInitOffset(String topic, String group, int queueId, int initMode, boolean init) throws ConsumeQueueException { long offset; From c8cb465ad5974ebde253544c916e8219409f8900 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Mon, 18 May 2026 20:34:45 +0800 Subject: [PATCH 022/101] comment: add process comments to method getInitOffset of PopMessageProcessor --- .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 7bdde5339f7..d260951ea59 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -1014,9 +1014,13 @@ private long getPopOffset(String topic, String group, int queueId, int initMode, long offset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, queueId); if (offset < 0) { + //the first time consume, init offset by initMode offset = this.getInitOffset(topic, group, queueId, initMode, init); } + // before lock checkResetOffset is false + // after lock checkResetOffset is true + // This is an admin related feature if (checkResetOffset) { Long resetOffset = resetPopOffset(topic, group, queueId); if (resetOffset != null) { From 0cab192e38378e8412fc9482c8aff30c75ebea49 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 19 May 2026 21:00:38 +0800 Subject: [PATCH 023/101] comment: add class comments to PopBufferMergeService --- .../processor/PopBufferMergeService.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 5373eaea333..07128d0dfa3 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -44,8 +44,37 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; +/** + * File based Ack buffer merge service. + * + *

    buffer checkpoint in memory then enqueue them into system revive queue then wait to be acked. + * + *

    Two in-memory data structures drive the merge logic: + *

      + *
    • {@link #buffer} — maps {@code mergeKey} to {@link PopCheckPointWrapper}, + * tracking which sub-messages within a CK batch have been acked + * (via {@code bits} bitmask) and which have been persisted + * (via {@code toStoreBits} bitmask)
    • + *
    • {@link #commitOffsets} — maps {@code topic@cid@queueId} to an ordered + * queue of {@link PopCheckPointWrapper}s for sequential offset committing
    • + *
    + * + *

    The background {@link #scan()} thread periodically evaluates each buffered CK: + *

      + *
    • All acks received — removes the CK from the buffer without writing + * anything to storage (clean completion)
    • + *
    • About to expire ({@code reviveTime - now < popCkStayBufferTimeOut}) + * or stayed too long — writes the CK and all un-persisted acks + * (or batch acks) to the revive topic
    • + *
    + * + *

    This service is enabled by {@code enablePopBufferMerge} and only runs on + * a master or a slave acting as master. When {@code enablePopBatchAck} is set, + * multiple ack offsets are packed into a single {@link BatchAckMsg}. + */ public class PopBufferMergeService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); + // mergeKey: topic + group + queueId + startOffset + popTime + brokerName ConcurrentHashMap buffer = new ConcurrentHashMap<>(1024 * 16); ConcurrentHashMap> commitOffsets = From 4f612a9801b33e46be0e3112ca93cd31d0e174bf Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 21 May 2026 16:15:33 +0800 Subject: [PATCH 024/101] comment: add class comments and attribute comments to PopCheckPoint --- .../rocketmq/store/pop/PopCheckPoint.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java index 803ebc68957..bbd030650f1 100644 --- a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -21,22 +21,46 @@ import java.util.ArrayList; import java.util.List; +/** + * state check info for multi-messages pop from consume queue + */ public class PopCheckPoint implements Comparable { @JSONField(name = "so") private long startOffset; + /** + * pop time, which is the time when message is popped + * reviveTime = popTime + invisibleTime + */ @JSONField(name = "pt") private long popTime; + /** + * the invisible time of messages + * default is 60s, it can be changed by MQ client + */ @JSONField(name = "it") private long invisibleTime; + /** + * store ack states of messages + * one byte for each message + */ @JSONField(name = "bm") private int bitMap; + /** + * total number of messages + */ @JSONField(name = "n") private byte num; @JSONField(name = "q") private int queueId; @JSONField(name = "t") private String topic; + /** + * consumer group + */ private String cid; + /** + * revive offset, which is the consume queue offset of messageExt + */ @JSONField(name = "ro") private long reviveOffset; @JSONField(name = "d") From baef8762a9478829d7d16de4f890a5418e886fc1 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 15:19:16 +0800 Subject: [PATCH 025/101] comment: add attribute comments to PopCheckPointWrapper --- .../broker/processor/PopBufferMergeService.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 07128d0dfa3..06340999f8d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -466,10 +466,10 @@ private boolean checkQueueOk(PopCheckPointWrapper pointWrapper) { /** * put to store && add to buffer. * - * @param point - * @param reviveQueueId - * @param reviveQueueOffset - * @param nextBeginOffset + * @param point check point + * @param reviveQueueId revive queueId + * @param reviveQueueOffset revive queueOffset + * @param nextBeginOffset next offset * @return */ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, @@ -874,14 +874,18 @@ public class PopCheckPointWrapper { // -1: not stored, >=0: stored, Long.MAX: storing. private volatile long reviveQueueOffset; private final PopCheckPoint ck; - // bit for concurrent + // bits for concurrent private final AtomicInteger bits; // bit for stored buffer ak private final AtomicInteger toStoreBits; + // nextOffset of original topic private final long nextBeginOffset; + // topic@group@queueId private final String lockKey; + // topic + group + queueId + startOffset + popTime + brokerName private final String mergeKey; private final boolean justOffset; + // whether check point has stored in revive queue private volatile boolean ckStored = false; public PopCheckPointWrapper(int reviveQueueId, long reviveQueueOffset, PopCheckPoint point, From dac6f4eb2351e40b7cd6bd5e621e50e84b31fd09 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 15:26:26 +0800 Subject: [PATCH 026/101] comment: add method comments to addCkMock of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 06340999f8d..f0f70e7d999 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -494,6 +494,14 @@ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long revi return true; } + /** + * mock checkpoint then add to buffer. + * this method is called when popped message is: + * - NO_MATCHED_MESSAGE + * - OFFSET_FOUND_NULL + * - MESSAGE_WAS_REMOVING + * - NO_MATCHED_LOGIC_QUEUE + */ public void addCkMock(String group, String topic, int queueId, long startOffset, long invisibleTime, long popTime, int reviveQueueId, long nextBeginOffset, String brokerName) { final PopCheckPoint ck = new PopCheckPoint(); From ffea2f42447f357e85d838378f50015ce32e3988 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 15:29:40 +0800 Subject: [PATCH 027/101] comment: add method comments to addCk of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index f0f70e7d999..876e7ccb29e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -465,12 +465,13 @@ private boolean checkQueueOk(PopCheckPointWrapper pointWrapper) { /** * put to store && add to buffer. + * addAndStoreCheckpoint maybe a better name. * * @param point check point * @param reviveQueueId revive queueId * @param reviveQueueOffset revive queueOffset * @param nextBeginOffset next offset - * @return + * @return true if success */ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { @@ -524,6 +525,9 @@ public void addCkMock(String group, String topic, int queueId, long startOffset, } } + /** + * add checkpoint to buffer. + */ public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { // key: point.getT() + point.getC() + point.getQ() + point.getSo() + point.getPt() if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { From 31a456dcc764ce93c9c8084e174c3ba6dd13f04c Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 15:40:00 +0800 Subject: [PATCH 028/101] comment: add method comments to putCkToStore of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 876e7ccb29e..5aca7d7aae2 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -649,6 +649,12 @@ public void clearOffsetQueue(String lockKey) { this.commitOffsets.remove(lockKey); } + /** + * write message(checkpoint) to revive topic, then update pointWrapper related info. + * + * @param pointWrapper checkpoint + * @param runInCurrent async or sync + */ private void putCkToStore(final PopCheckPointWrapper pointWrapper, final boolean runInCurrent) { if (pointWrapper.getReviveQueueOffset() >= 0) { return; @@ -658,6 +664,7 @@ private void putCkToStore(final PopCheckPointWrapper pointWrapper, final boolean // Indicates that ck message is storing pointWrapper.setReviveQueueOffset(Long.MAX_VALUE); + // default value of isAppendCkAsync is false if (brokerController.getBrokerConfig().isAppendCkAsync() && runInCurrent) { brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handleCkMessagePutResult(putMessageResult, pointWrapper); From 81a3a140351896828dcbbff09e42acc35c767256 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 20:10:19 +0800 Subject: [PATCH 029/101] comment: add attribute comments to buffer and commitOffsets of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 5aca7d7aae2..30df279410c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -74,9 +74,26 @@ */ public class PopBufferMergeService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); - // mergeKey: topic + group + queueId + startOffset + popTime + brokerName + /** + * In-memory cache of check points. + * Key: topic + group + queueId + startOffset + popTime + brokerName + * Value: check point wrapper + * use cases: + * - scan: iterate buffer + * - addAckMsg: get check point from buffer and mark ack state of Check Point + */ ConcurrentHashMap buffer = new ConcurrentHashMap<>(1024 * 16); + /** + * manager check point for given consumer and given queue + * Key: topic@cid@queueId + * Value: check point queue of specific consumer and queue + * use cases: + * - getLatestOffset: get consumer next start offset of given queue + * - scanGarbage + * - getOffsetTotalSize: get total popping num + * - isQueueFull + */ ConcurrentHashMap> commitOffsets = new ConcurrentHashMap<>(); private volatile boolean serving = true; @@ -444,6 +461,8 @@ private boolean commitOffset(final PopCheckPointWrapper wrapper) { private boolean putOffsetQueue(PopCheckPointWrapper pointWrapper) { QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey()); + + // init with empty queue if (queue == null) { queue = new QueueWithTime<>(); QueueWithTime old = this.commitOffsets.putIfAbsent(pointWrapper.getLockKey(), queue); @@ -451,6 +470,7 @@ private boolean putOffsetQueue(PopCheckPointWrapper pointWrapper) { queue = old; } } + queue.setTime(pointWrapper.getCk().getPopTime()); return queue.get().offer(pointWrapper); } @@ -484,6 +504,8 @@ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long revi return false; } + // called before buffer operation + // because store operation will update attributes of pointWrapper this.putCkToStore(pointWrapper, checkQueueOk(pointWrapper)); putOffsetQueue(pointWrapper); @@ -496,7 +518,7 @@ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long revi } /** - * mock checkpoint then add to buffer. + * mock checkpoint then add it to offset queue. * this method is called when popped message is: * - NO_MATCHED_MESSAGE * - OFFSET_FOUND_NULL @@ -505,6 +527,7 @@ public boolean addCkJustOffset(PopCheckPoint point, int reviveQueueId, long revi */ public void addCkMock(String group, String topic, int queueId, long startOffset, long invisibleTime, long popTime, int reviveQueueId, long nextBeginOffset, String brokerName) { + // create checkpoint final PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) 0); @@ -520,6 +543,7 @@ public void addCkMock(String group, String topic, int queueId, long startOffset, pointWrapper.setCkStored(true); putOffsetQueue(pointWrapper); + if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ck just offset, mocked, {}", pointWrapper); } @@ -529,6 +553,7 @@ public void addCkMock(String group, String topic, int queueId, long startOffset, * add checkpoint to buffer. */ public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) { + // validate env and checkpoint // key: point.getT() + point.getC() + point.getQ() + point.getSo() + point.getPt() if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { return false; From e6a279f918cf61daa2ab9ee5e051eb746dd5d535 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 20:16:21 +0800 Subject: [PATCH 030/101] comment: add method comments to putOffsetQueue of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 30df279410c..72a462ab253 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -75,7 +75,7 @@ public class PopBufferMergeService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); /** - * In-memory cache of check points. + * In-memory map of check points. * Key: topic + group + queueId + startOffset + popTime + brokerName * Value: check point wrapper * use cases: @@ -85,7 +85,7 @@ public class PopBufferMergeService extends ServiceThread { ConcurrentHashMap buffer = new ConcurrentHashMap<>(1024 * 16); /** - * manager check point for given consumer and given queue + * manage check point of given consumer and given queue * Key: topic@cid@queueId * Value: check point queue of specific consumer and queue * use cases: @@ -459,6 +459,21 @@ private boolean commitOffset(final PopCheckPointWrapper wrapper) { return true; } + /** + * Enqueue the checkpoint wrapper into the per-{@code topic@cid@queueId} offset queue + * for sequential offset committing. + * + *

    The queue is maintained in FIFO order. The {@link #scanCommitOffset()} method + * drains the queue from the head, ensuring that offsets are committed in the same + * order as the checkpoints were created, which prevents consumer offset regression. + * + *

    The {@link QueueWithTime#time} is also updated to the CK's pop time so that + * {@link #scanGarbage()} can identify and remove stale entries after 5 minutes of + * inactivity. + * + * @param pointWrapper the checkpoint wrapper to enqueue + * @return true if the element was added to the queue successfully + */ private boolean putOffsetQueue(PopCheckPointWrapper pointWrapper) { QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey()); From 6a247b522a753fd6d93724069675553755d3f925 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 21:35:53 +0800 Subject: [PATCH 031/101] comment: add process comments to method run of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 72a462ab253..5beb8d5b3a0 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -138,6 +138,7 @@ public void run() { // scan while (!this.isStopped()) { try { + // env check if (!isShouldRunning()) { // slave this.waitForRunning(interval * 200 * 5); @@ -153,8 +154,8 @@ public void run() { scanGarbage(); } + // waiting this.waitForRunning(interval); - if (!this.serving && this.buffer.size() == 0 && getOffsetTotalSize() == 0) { this.serving = true; } @@ -164,6 +165,7 @@ public void run() { } } + // scan until buffer is empty this.serving = false; try { Thread.sleep(2000); From f5a0fc6c6af0ba192ff8f150ab5f3dbbdb23d41f Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 21:42:38 +0800 Subject: [PATCH 032/101] comment: add method comments to scanGarbage of PopBufferMergeService --- .../broker/processor/PopBufferMergeService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 5beb8d5b3a0..3a6b617b128 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -233,9 +233,20 @@ public long getLatestOffset(String topic, String group, int queueId) { return getLatestOffset(KeyBuilder.buildPollingKey(topic, group, queueId)); } + /** + * Remove stale entries from {@link #commitOffsets}. + * + *

    Three types of entries are removed: + *

      + *
    • Topic no longer exists (deleted)
    • + *
    • Consumer group no longer exists (unsubscribed)
    • + *
    • No activity for more than 5 minutes (idle)
    • + *
    + */ private void scanGarbage() { Iterator>> iterator = commitOffsets.entrySet().iterator(); while (iterator.hasNext()) { + // validate checkpoint Map.Entry> entry = iterator.next(); if (entry.getKey() == null) { continue; @@ -256,6 +267,7 @@ private void scanGarbage() { iterator.remove(); continue; } + if (System.currentTimeMillis() - entry.getValue().getTime() > minute5) { POP_LOGGER.info("[PopBuffer]remove long time not used sub {} of topic {} in buffer!", cid, topic); iterator.remove(); From a3ea07bcf1a6504fccad2cbc8547fbc104e9706b Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Sat, 23 May 2026 22:53:04 +0800 Subject: [PATCH 033/101] comment: add method comments to scan of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 3a6b617b128..2fc0f3d9727 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -151,6 +151,7 @@ public void run() { scan(); if (scanTimes % countOfSecond30 == 0) { + // remove checkpoint which are timeout scanGarbage(); } @@ -283,6 +284,26 @@ private boolean isSubscriptionGroupNotExist(PopCheckPointWrapper pointWrapper) { } + /** + * Scan and process all buffered checkpoints, then drain the offset commit queue. + * + *

    For each entry in {@link #buffer}: + *

      + *
    • Consumer group not found — removes the entry silently
    • + *
    • CK done (all sub-messages acked) — removes from buffer, no store write needed
    • + *
    • Just-offset entry — writes the CK to the revive topic if not yet stored
    • + *
    • Needs eviction (service stopped, revive timeout, or stay timeout) — + * writes the CK and all un-persisted acks (batch or individual) to the revive topic, + * then removes the entry when all persisted
    • + *
    • Otherwise — leaves the entry in the buffer for the next scan cycle
    • + *
    + * + *

    After processing the buffer, calls {@link #scanCommitOffset()} to commit offsets + * for finished checkpoints in FIFO order. + * + *

    If the scan duration exceeds {@code popCkStayBufferTimeOut - 1000ms}, the service + * temporarily stops accepting new CKs ({@link #serving} = false) to avoid backlog. + */ private void scan() { long startTime = System.currentTimeMillis(); AtomicInteger count = new AtomicInteger(0); @@ -319,6 +340,7 @@ private void scan() { PopCheckPoint point = pointWrapper.getCk(); long now = System.currentTimeMillis(); + // check whether check point is timeout boolean removeCk = !this.serving; // ck will be timeout if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut()) { From 848f23f5a1f5b10f0f031f014b7bdd8d9e856a13 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 10:12:00 +0800 Subject: [PATCH 034/101] comment: add process comments to method addAk of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 2fc0f3d9727..e50f7327899 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -325,7 +325,6 @@ private void scan() { continue; } - // just process offset(already stored at pull thread), or buffer ck(not stored and ack finish) if (pointWrapper.isJustOffset() && pointWrapper.isCkStored() || isCkDone(pointWrapper) || isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { @@ -357,16 +356,16 @@ private void scan() { } // double check - if (isCkDone(pointWrapper)) { + if (isCkDone(pointWrapper)) { // ck done, do nothing continue; - } else if (pointWrapper.isJustOffset()) { + } else if (pointWrapper.isJustOffset()) { // store check point // just offset should be in store. if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); countCk++; } continue; - } else if (removeCk) { + } else if (removeCk) { // store or merge ack info // put buffer ak to store if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); @@ -377,11 +376,12 @@ private void scan() { continue; } - if (brokerController.getBrokerConfig().isEnablePopBatchAck()) { + if (brokerController.getBrokerConfig().isEnablePopBatchAck()) { // default is false List indexList = this.batchAckIndexList; try { for (byte i = 0; i < point.getNum(); i++) { // reput buffer ak to store + // if checkpoint is acked and not stored, add to indexList if (DataConverter.getBit(pointWrapper.getBits().get(), i) && !DataConverter.getBit(pointWrapper.getToStoreBits().get(), i)) { indexList.add(i); @@ -415,6 +415,7 @@ private void scan() { int offsetBufferSize = scanCommitOffset(); + // calculate scan times long eclipse = System.currentTimeMillis() - startTime; if (eclipse > brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() - 1000) { POP_LOGGER.warn("[PopBuffer]scan stop, because eclipse too long, PopBufferEclipse={}, " + @@ -649,13 +650,16 @@ public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOff } public boolean addAk(int reviveQid, AckMsg ackMsg) { + // validate env if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { return false; } if (!serving) { return false; } + try { + // get and validate checkpoint PopCheckPointWrapper pointWrapper = this.buffer.get(ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime() + ackMsg.getBrokerName()); if (pointWrapper == null) { if (brokerController.getBrokerConfig().isEnablePopLog()) { @@ -685,7 +689,8 @@ public boolean addAk(int reviveQid, AckMsg ackMsg) { return false; } - if (ackMsg instanceof BatchAckMsg) { + // merge ackMsg with checkpoint + if (ackMsg instanceof BatchAckMsg) { // merge batch ackMsg for (Long ackOffset : ((BatchAckMsg) ackMsg).getAckOffsetList()) { int indexOfAck = point.indexOfAck(ackOffset); if (indexOfAck > -1) { @@ -694,7 +699,7 @@ public boolean addAk(int reviveQid, AckMsg ackMsg) { POP_LOGGER.error("[PopBuffer]Invalid index of ack, reviveQid={}, {}, {}", reviveQid, ackMsg, point); } } - } else { + } else { // merge ackMsg int indexOfAck = point.indexOfAck(ackMsg.getAckOffset()); if (indexOfAck > -1) { markBitCAS(pointWrapper.getBits(), indexOfAck); @@ -704,6 +709,7 @@ public boolean addAk(int reviveQid, AckMsg ackMsg) { } } + // logging if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]add ack, rqId={}, {}, {}", reviveQid, pointWrapper, ackMsg); } From 00252be826e58656442eb156964bdfff346f9cd8 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 10:19:12 +0800 Subject: [PATCH 035/101] comment: add method comments to addAk of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index e50f7327899..4fcbf1df47d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -649,6 +649,28 @@ public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOff return true; } + /** + * Merge a consumer ack into the buffered checkpoint. + * + *

    The ack is not written to the revive topic immediately. Instead, a flag is + * set in {@link PopCheckPointWrapper#bits} via {@link #markBitCAS}. + * The pending ack will later be flushed to storage by {@link #scan()} when the + * checkpoint is evicted (timeout / buffer full / service stopping). + * + *

    Rejection conditions (return false): + *

      + *
    • {@code enablePopBufferMerge} is disabled
    • + *
    • The service is not serving (too busy)
    • + *
    • No matching checkpoint found in {@link #buffer}
    • + *
    • The checkpoint is a {@code justOffset} entry (no messages to ack)
    • + *
    • The checkpoint is too close to its revive deadline
    • + *
    • The checkpoint has been buffered for too long
    • + *
    + * + * @param reviveQid revive queue id (used only for logging) + * @param ackMsg the ack message from the consumer + * @return true if the ack was merged successfully + */ public boolean addAk(int reviveQid, AckMsg ackMsg) { // validate env if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) { From 801a041523120242db2a8df1cd959df3c1f96f2e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 11:01:33 +0800 Subject: [PATCH 036/101] comment: add attribute comments to queueOffsetDiff of PopCheckPoint --- .../apache/rocketmq/store/pop/PopCheckPoint.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java index bbd030650f1..2af92847604 100644 --- a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -63,6 +63,19 @@ public class PopCheckPoint implements Comparable { */ @JSONField(name = "ro") private long reviveOffset; + /** + * Per-message offset differences from {@link #startOffset}. + * + *

    When a batch of messages is popped, the queue offsets of the messages may not + * be contiguous (e.g. batch messages, ConsumeQueue compaction, filter mismatch gaps). + * This list records {@code actualQueueOffset - startOffset} for each message in the + * batch, so that the system can correctly map an ack offset back to its index within + * the checkpoint via {@link #indexOfAck}, and reconstruct the original offset via + * {@link #ackOffsetByIndex}. + * + *

    When this field is null or empty (old-version CK), offsets are assumed to be + * {@code startOffset + index}. + */ @JSONField(name = "d") private List queueOffsetDiff; @JSONField(name = "bn") From cd92c92740aea3de3a11f519504eab8e6ccf5dbd Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 11:20:21 +0800 Subject: [PATCH 037/101] comment: add notes about queueOffsetDiff null scenario of PopCheckPoint --- .../java/org/apache/rocketmq/store/pop/PopCheckPoint.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java index 2af92847604..0bdb23c9adc 100644 --- a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -65,6 +65,7 @@ public class PopCheckPoint implements Comparable { private long reviveOffset; /** * Per-message offset differences from {@link #startOffset}. + * queueOffsetDiff will not be null or empty in 5.* * *

    When a batch of messages is popped, the queue offsets of the messages may not * be contiguous (e.g. batch messages, ConsumeQueue compaction, filter mismatch gaps). @@ -207,7 +208,7 @@ public int indexOfAck(long ackOffset) { return -1; } - // old version of checkpoint + // old version of checkpoint, this will not happen in 5.* if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { if (ackOffset - startOffset < num) { @@ -222,7 +223,7 @@ public int indexOfAck(long ackOffset) { } public long ackOffsetByIndex(byte index) { - // old version of checkpoint + // old version of checkpoint, this will not happen in 5.* if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { return startOffset + index; } From e3287d4bca45f3e4fcdd7dfd9b74acca316208eb Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 19:42:29 +0800 Subject: [PATCH 038/101] comment: update attribute comments for bits and toStoreBits of PopCheckPointWrapper --- .../rocketmq/broker/processor/PopBufferMergeService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 4fcbf1df47d..02cdece1a88 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -997,9 +997,9 @@ public class PopCheckPointWrapper { // -1: not stored, >=0: stored, Long.MAX: storing. private volatile long reviveQueueOffset; private final PopCheckPoint ck; - // bits for concurrent + // store ack states of messages, one byte for each message private final AtomicInteger bits; - // bit for stored buffer ak + // bits for stored buffer ak, one byte for each message private final AtomicInteger toStoreBits; // nextOffset of original topic private final long nextBeginOffset; From 107d6fb1166ad1890b960bdd580a9343a9680047 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 19:47:08 +0800 Subject: [PATCH 039/101] comment: add class and method comments to DataConverter --- .../rocketmq/common/utils/DataConverter.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java index cc96770b22a..474179d52f8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java +++ b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java @@ -19,15 +19,33 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; +/** + * Bit-level utility methods, primarily used by Pop-mode ack tracking. + * + *

    An {@code int} bitmask is used to track the ack state of up to 32 sub-messages + * within a single Pop checkpoint (see {@code PopCheckPoint}). + */ public class DataConverter { public static final Charset CHARSET_UTF8 = Charset.forName("UTF-8"); + /** + * Convert a {@code long} to an 8-byte array (big-endian). + */ public static byte[] Long2Byte(Long v) { ByteBuffer tmp = ByteBuffer.allocate(8); tmp.putLong(v); return tmp.array(); } + /** + * Set or clear the bit at {@code index} in an int bitmask. + *

    Uses {@code 1L} (long literal) to avoid signed-int overflow when {@code index == 31}. + * + * @param value the original bitmask + * @param index the bit position (0-based, 0..31) + * @param flag {@code true} to set, {@code false} to clear + * @return the updated bitmask + */ public static int setBit(int value, int index, boolean flag) { if (flag) { return (int) (value | (1L << index)); @@ -36,6 +54,13 @@ public static int setBit(int value, int index, boolean flag) { } } + /** + * Test whether the bit at {@code index} is set in an int bitmask. + * + * @param value the bitmask + * @param index the bit position (0-based, 0..31) + * @return {@code true} if the bit is 1 + */ public static boolean getBit(int value, int index) { return (value & (1L << index)) != 0; } From 404019b34e7996fef24594f88f646107f4cc2553 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 19:49:56 +0800 Subject: [PATCH 040/101] comment: add method comments to markBitCAS of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 02cdece1a88..6d48ca18970 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -453,6 +453,15 @@ public int getBufferedCKSize() { return this.counter.get(); } + /** + * Atomically set the bit at {@code index} in an {@link AtomicInteger} bitmask. + * + *

    Uses a CAS (compare-and-swap) loop to ensure thread safety without locking. + * If the bit is already set, this method returns immediately (no-op). + * + * @param setBits the atomic bitmask to update + * @param index the bit position (0-based) + */ private void markBitCAS(AtomicInteger setBits, int index) { while (true) { int bits = setBits.get(); From f49cbe4566e152f95c8fa17d888879ad94589375 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 19:52:43 +0800 Subject: [PATCH 041/101] comment: add method comments to indexOfAck of PopCheckPoint --- .../org/apache/rocketmq/store/pop/PopCheckPoint.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java index 0bdb23c9adc..1bfdf6c3d83 100644 --- a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -203,6 +203,17 @@ public void addDiff(int diff) { this.queueOffsetDiff.add(diff); } + /** + * Map an ack offset to its index within the checkpoint batch. + * + *

    The index is used to look up the corresponding bit in the {@link #bitMap} + * (or in {@code PopCheckPointWrapper.bits}) and to retrieve the original + * queue offset via {@link #ackOffsetByIndex}. + * + * @param ackOffset the queue offset being acked + * @return the sub-message index (0-based), or -1 if the offset is not found + * in this checkpoint + */ public int indexOfAck(long ackOffset) { if (ackOffset < startOffset) { return -1; From b98be603aec543918963e7c60721e21b324ca04a Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 20:06:09 +0800 Subject: [PATCH 042/101] comment: add method comments to ackOffsetByIndex of PopCheckPoint --- .../java/org/apache/rocketmq/store/pop/PopCheckPoint.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java index 1bfdf6c3d83..e4ed5c085e8 100644 --- a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -233,6 +233,14 @@ public int indexOfAck(long ackOffset) { return queueOffsetDiff.indexOf((int) (ackOffset - startOffset)); } + /** + * get original queue offset by index. + * the method name is miss-leading, it should be getQueueOffsetByIndex. + * queueOffset = startOffset + queueOffsetDiff[index] + * + * @param index sub-message index within this checkpoint (0-based) + * @return the original queue offset in the consume queue + */ public long ackOffsetByIndex(byte index) { // old version of checkpoint, this will not happen in 5.* if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { From 1049c19258d4414c6f9d649173ab7b1ffc181ffc Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 20:38:47 +0800 Subject: [PATCH 043/101] comment: add inline comments to putAckToStore and scan of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 6d48ca18970..b7be5635e5b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -396,6 +396,7 @@ private void scan() { } else { for (byte i = 0; i < point.getNum(); i++) { // reput buffer ak to store + // if checkpoint is acked and not stored, call putAckToStore if (DataConverter.getBit(pointWrapper.getBits().get(), i) && !DataConverter.getBit(pointWrapper.getToStoreBits().get(), i)) { putAckToStore(pointWrapper, i, count); @@ -817,6 +818,7 @@ private void handleCkMessagePutResult(PutMessageResult putMessageResult, final P } private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgIndex, AtomicInteger count) { + // build ackMsg and Message by checkpoint PopCheckPoint point = pointWrapper.getCk(); MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); final AckMsg ackMsg = new AckMsg(); @@ -840,7 +842,8 @@ private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgInde msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - if (brokerController.getBrokerConfig().isAppendAckAsync()) { + // store message then change store status of the checkpoint + if (brokerController.getBrokerConfig().isAppendAckAsync()) { // default value is false brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handleAckPutMessageResult(ackMsg, putMessageResult, pointWrapper, count, msgIndex); }).exceptionally(throwable -> { @@ -848,7 +851,9 @@ private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgInde return null; }); } else { + // store message PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + // change store status of the checkpoint handleAckPutMessageResult(ackMsg, putMessageResult, pointWrapper, count, msgIndex); } } From 6c561f5d9a7cf7cf78f195bae98aedec5bc5aab4 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 20:43:45 +0800 Subject: [PATCH 044/101] comment: add method comments to putAckToStore of PopBufferMergeService --- .../broker/processor/PopBufferMergeService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index b7be5635e5b..9dce73d48c8 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -817,6 +817,19 @@ private void handleCkMessagePutResult(PutMessageResult putMessageResult, final P } } + /** + * Persist message which created by checkpoint to the revive topic. + * + *

      + *
    • create message by checkpoint
    • + *
    • write message to revive topic
    • + *
    • update pointWrapper related info
    • + *
    + * + * @param pointWrapper the checkpoint wrapper containing the original CK + * @param msgIndex the sub-message index within the CK batch to ack + * @param count atomic counter incremented on successful persistence + */ private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgIndex, AtomicInteger count) { // build ackMsg and Message by checkpoint PopCheckPoint point = pointWrapper.getCk(); From ca473541efe0a935d948a62c7b8ddadc3b1194ae Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 20:45:47 +0800 Subject: [PATCH 045/101] comment: add method comments to handleAckPutMessageResult of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 9dce73d48c8..f71be2dfe52 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -871,6 +871,15 @@ private void putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgInde } } + /** + * update store status of checkpoint if revive message stored successfully. + * + * @param ackMsg the ack message that was persisted + * @param putMessageResult the result returned by the store + * @param pointWrapper the checkpoint wrapper being processed + * @param count atomic counter incremented on success + * @param msgIndex the sub-message index that was persisted + */ private void handleAckPutMessageResult(AckMsg ackMsg, PutMessageResult putMessageResult, PopCheckPointWrapper pointWrapper, AtomicInteger count, byte msgIndex) { brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus()); From 2eb0e9074c8cdc4e9d9dea1605d04eb18cf5f0be Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Tue, 26 May 2026 22:18:46 +0800 Subject: [PATCH 046/101] comment: add attribute comments to justOffset of PopCheckPointWrapper --- .../broker/processor/PopBufferMergeService.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index f71be2dfe52..491fa68e1d7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -1043,6 +1043,20 @@ public class PopCheckPointWrapper { private final String lockKey; // topic + group + queueId + startOffset + popTime + brokerName private final String mergeKey; + /** + * Whether this checkpoint should be written to the revive topic directly. + * + *

    When {@code true}: + *

      + *
    • The CK has already been or will be written to the revive topic directly
    • + *
    • No Ack merging is needed — {@link #addAk} rejects these entries
    • + *
    • The wrapper exists solely to maintain FIFO offset commit order in + * {@link #commitOffsets}
    • + *
    + * + * @see PopBufferMergeService#addCkJustOffset + * @see PopBufferMergeService#addCkMock + */ private final boolean justOffset; // whether check point has stored in revive queue private volatile boolean ckStored = false; From 6c554aeeaecd48f752878e34845976e10415c5a0 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:02:57 +0800 Subject: [PATCH 047/101] comment: update attribute comments to reviveQueueOffset of PopCheckPointWrapper --- .../broker/processor/PopBufferMergeService.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 491fa68e1d7..09f19b4f138 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -1030,7 +1030,18 @@ public LinkedBlockingDeque get() { public class PopCheckPointWrapper { private final int reviveQueueId; - // -1: not stored, >=0: stored, Long.MAX: storing. + /** + * The consume queue offset of the CK message in the revive topic. + * + *

    Three-state indicator: + *

      + *
    • {@code -1} — not yet stored; {@link #putCkToStore} will write it
    • + *
    • {@code >= 0} — successfully stored; the value is the offset in the + * revive topic's consume queue
    • + *
    • {@link Long#MAX_VALUE} — a write is in progress (prevents duplicate + * writes from concurrent scans)
    • + *
    + */ private volatile long reviveQueueOffset; private final PopCheckPoint ck; // store ack states of messages, one byte for each message From d3d95875621ff9b52c47c193bb840869bebe9775 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:04:43 +0800 Subject: [PATCH 048/101] comment: add inline comment for reviveQueueOffset check in scan --- .../apache/rocketmq/broker/processor/PopBufferMergeService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 09f19b4f138..0fa357979c6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -367,6 +367,7 @@ private void scan() { continue; } else if (removeCk) { // store or merge ack info // put buffer ak to store + // revive queue offset < 0 means checkpoint was not stored if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); countCk++; From a56679bc1960e39d8744c422c9c0957f74833ac7 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:06:55 +0800 Subject: [PATCH 049/101] comment: fix inline comments in scan --- .../rocketmq/broker/processor/PopBufferMergeService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 0fa357979c6..bc856077f40 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -358,14 +358,14 @@ private void scan() { // double check if (isCkDone(pointWrapper)) { // ck done, do nothing continue; - } else if (pointWrapper.isJustOffset()) { // store check point + } else if (pointWrapper.isJustOffset()) { // store checkpoint // just offset should be in store. if (pointWrapper.getReviveQueueOffset() < 0) { putCkToStore(pointWrapper, this.brokerController.getBrokerConfig().isAppendCkAsync()); countCk++; } continue; - } else if (removeCk) { // store or merge ack info + } else if (removeCk) { // store checkpoint if needed // put buffer ak to store // revive queue offset < 0 means checkpoint was not stored if (pointWrapper.getReviveQueueOffset() < 0) { From fdfc788c21e30b2e7053b357e18ed034f4d640a1 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:08:17 +0800 Subject: [PATCH 050/101] comment: update inline comment for isCkDone check in scan --- .../apache/rocketmq/broker/processor/PopBufferMergeService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index bc856077f40..b5959804bc7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -356,7 +356,7 @@ private void scan() { } // double check - if (isCkDone(pointWrapper)) { // ck done, do nothing + if (isCkDone(pointWrapper)) { // all checkpoint are acked, do nothing continue; } else if (pointWrapper.isJustOffset()) { // store checkpoint // just offset should be in store. From d138c0c2c435ff9c51eb3dfc27fe625f35eee5f6 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:11:16 +0800 Subject: [PATCH 051/101] comment: add method comments to isCkDone of PopBufferMergeService --- .../broker/processor/PopBufferMergeService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index b5959804bc7..4ee7ec457f6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -986,6 +986,17 @@ private boolean cancelCkTimer(final PopCheckPointWrapper pointWrapper) { return true; } + /** + * Check whether all sub-messages in the checkpoint have been acked. + * + *

    Every sub-message has a corresponding bit in + * {@link PopCheckPointWrapper#bits}. This method returns {@code true} when + * all bits are set, meaning the CK can be removed from the buffer without + * writing any ack to the revive topic (clean completion). + * + * @param pointWrapper the checkpoint wrapper to check + * @return {@code true} if every sub-message has been acked + */ private boolean isCkDone(PopCheckPointWrapper pointWrapper) { byte num = pointWrapper.getCk().getNum(); for (byte i = 0; i < num; i++) { From 1246674c0b9f16296331ba5c214c09dd4b8fb677 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:14:12 +0800 Subject: [PATCH 052/101] comment: add method comments to isCkDoneForFinish of PopBufferMergeService --- .../broker/processor/PopBufferMergeService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 4ee7ec457f6..bb01f7685f5 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -1007,6 +1007,18 @@ private boolean isCkDone(PopCheckPointWrapper pointWrapper) { return true; } + /** + * Check whether all acked sub-messages have been fully persisted. + * + *

    Uses XOR: {@code bits ^ toStoreBits}. A bit is set in the result when + * the corresponding sub-message has been acked ({@code bits}) but not yet + * persisted ({@code toStoreBits}). Returns {@code true} only when every + * acked message has also been persisted, meaning the checkpoint is ready + * for final cleanup. + * + * @param pointWrapper the checkpoint wrapper to check + * @return {@code true} if no ack remains to be persisted + */ private boolean isCkDoneForFinish(PopCheckPointWrapper pointWrapper) { byte num = pointWrapper.getCk().getNum(); int bits = pointWrapper.getBits().get() ^ pointWrapper.getToStoreBits().get(); From 0f4a4cb25ee29e8df781a2851c8aeddab3a62ccd Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:23:39 +0800 Subject: [PATCH 053/101] comment: add method comments to scanCommitOffset of PopBufferMergeService --- .../processor/PopBufferMergeService.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index bb01f7685f5..9a51fe446e3 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -182,6 +182,26 @@ public void run() { } } + /** + * Drain the {@link #commitOffsets} queues and commit consumer offsets in FIFO order. + * + *

    For each {@code topic@cid@queueId} queue, the method peeks the head (oldest) + * wrapper and checks whether it is ready to commit: + *

      + *
    • Just-offset entry with CK stored
    • + *
    • All sub-messages acked ({@link #isCkDone})
    • + *
    • All acks persisted and CK stored ({@link #isCkDoneForFinish})
    • + *
    + * + *

    If the head is ready, it is committed and removed. Processing continues + * to the next wrapper in the same queue. If the head is not ready, the loop + * breaks — this ensures strict FIFO order and prevents consumer offset + * regression. + * + *

    Called at the end of {@link #scan()} after the buffer has been processed. + * + * @return the total number of remaining wrappers across all queues (for logging) + */ private int scanCommitOffset() { Iterator>> iterator = this.commitOffsets.entrySet().iterator(); int count = 0; From fbd9a5d44cd0df9727698b05b80384d558ffa751 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:28:59 +0800 Subject: [PATCH 054/101] comment: add naming note to scanCommitOffset --- .../apache/rocketmq/broker/processor/PopBufferMergeService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 9a51fe446e3..64126798bbf 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -184,6 +184,7 @@ public void run() { /** * Drain the {@link #commitOffsets} queues and commit consumer offsets in FIFO order. + * scanAndCommitOffset may be a better name * *

    For each {@code topic@cid@queueId} queue, the method peeks the head (oldest) * wrapper and checks whether it is ready to commit: From 57b50896ce4140e0565dd22ca456ad89b325e25c Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:29:54 +0800 Subject: [PATCH 055/101] comment: add method comments to commitOffset of PopBufferMergeService --- .../broker/processor/PopBufferMergeService.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 64126798bbf..36f6e5ae66c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -499,6 +499,22 @@ private void markBitCAS(AtomicInteger setBits, int index) { } } + /** + * Commit the consumer offset for the checkpoint's {@code topic@cid@queueId}. + * + *

    Called from {@link #scanCommitOffset()} after the checkpoint is confirmed + * as finished (all acks received or CK stored). The offset is advanced to + * {@link PopCheckPointWrapper#nextBeginOffset}, which is the offset of the + * first message after this batch. + * + *

    The operation is guarded by {@link PopMessageProcessor.QueueLockManager} + * to prevent concurrent offset updates on the same queue. + * + * @param wrapper the finished checkpoint wrapper + * @return {@code true} if the offset was committed or no commit is needed + * ({@code nextBeginOffset < 0}); {@code false} if the lock could + * not be acquired (caller should retry later) + */ private boolean commitOffset(final PopCheckPointWrapper wrapper) { if (wrapper.getNextBeginOffset() < 0) { return true; From 5278b4b36f578634fc99a4d0346a0ea20ad0f20a Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 07:31:28 +0800 Subject: [PATCH 056/101] comment: add inline comment for scanCommitOffset call in scan --- .../apache/rocketmq/broker/processor/PopBufferMergeService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 36f6e5ae66c..c674cb7ef17 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -436,6 +436,7 @@ private void scan() { } } + // scan commitOffsets and commit offset which is needed. int offsetBufferSize = scanCommitOffset(); // calculate scan times From 224815b44643e9df1b6943bb7324c02339ba9eb6 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 08:51:27 +0800 Subject: [PATCH 057/101] comment: add inline comments for store and remove in scan --- .../apache/rocketmq/broker/processor/PopBufferMergeService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index c674cb7ef17..5a745b2a355 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -398,6 +398,7 @@ private void scan() { continue; } + // store checkpoint if (brokerController.getBrokerConfig().isEnablePopBatchAck()) { // default is false List indexList = this.batchAckIndexList; try { @@ -426,6 +427,7 @@ private void scan() { } } + // remove checkpoint from buffer if (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("[PopBuffer]ck finish, {}", pointWrapper); From 26c908dada7f127e0152976b4c23a54977985b57 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 08:57:42 +0800 Subject: [PATCH 058/101] comment: add in-line comments to method scanGarbage of PopBufferMergeService --- .../rocketmq/broker/processor/PopBufferMergeService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java index 5a745b2a355..b17c1c7e410 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java @@ -279,17 +279,23 @@ private void scanGarbage() { } String topic = keyArray[0]; String cid = keyArray[1]; + + // remove if topic no longer exists if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) { POP_LOGGER.info("[PopBuffer]remove nonexistent topic {} in buffer!", topic); iterator.remove(); continue; } + + // remove if subscription group no longer exists if (!brokerController.getSubscriptionGroupManager().containsSubscriptionGroup(cid)) { POP_LOGGER.info("[PopBuffer]remove nonexistent subscription group {} of topic {} in buffer!", cid, topic); iterator.remove(); continue; } + // remove if idle + // entry.getValue().getTime() = popTime of last checkpoint enqueued in the queue if (System.currentTimeMillis() - entry.getValue().getTime() > minute5) { POP_LOGGER.info("[PopBuffer]remove long time not used sub {} of topic {} in buffer!", cid, topic); iterator.remove(); From 05e2f4d6d003a381c34df4b79324a789bcebc36e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 09:00:36 +0800 Subject: [PATCH 059/101] comment: add class comments to PopReviveService --- .../broker/processor/PopReviveService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 07f16e98965..978b85b06eb 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -64,6 +64,23 @@ import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_IS_SYSTEM; import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.LABEL_TOPIC; +/** + * Per-queue service that reads the revive topic, matches checkpoints with AckMsgs, and + * revives timed-out messages by re-publishing them to the retry topic. + * + *

    Each revive queue has its own dedicated {@code PopReviveService} instance. + * The service periodically: + *

      + *
    1. Scans the revive topic ({@link #consumeReviveMessage}) to collect CK + * (checkpoint) and Ack messages, merging Acks into CK's bitMap
    2. + *
    3. Processes expired CKs ({@link #mergeAndRevive}) by re-publishing any + * un-acked sub-messages back to the retry topic via {@link #reviveRetry}
    4. + *
    + * + *

    This is the file-based revive path (CK + Ack messages are stored in + * the system revive topic). It is complemented by the KVStore-based path in + * {@code PopConsumerService} which handles the {@code PopConsumerKVStore} flow. + */ public class PopReviveService extends ServiceThread { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final int[] ckRewriteIntervalsInSeconds = new int[] { 10, 20, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200 }; From 352e5691a18dc34a25a1c039389896cb973f3c08 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 13:30:52 +0800 Subject: [PATCH 060/101] comment: add note about public methods to PopReviveService --- .../org/apache/rocketmq/broker/processor/PopReviveService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 978b85b06eb..03464b12795 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -68,6 +68,8 @@ * Per-queue service that reads the revive topic, matches checkpoints with AckMsgs, and * revives timed-out messages by re-publishing them to the retry topic. * + *

    There is only one public method for business: run

    + * *

    Each revive queue has its own dedicated {@code PopReviveService} instance. * The service periodically: *

      From 3ebb73454e0cadea341d84ecacbd92acbbce2360 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 13:39:36 +0800 Subject: [PATCH 061/101] comment: add in-line comments to method run of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 03464b12795..2c6faf77797 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -75,7 +75,7 @@ *
        *
      1. Scans the revive topic ({@link #consumeReviveMessage}) to collect CK * (checkpoint) and Ack messages, merging Acks into CK's bitMap
      2. - *
      3. Processes expired CKs ({@link #mergeAndRevive}) by re-publishing any + *
      4. Processes expired checkpoints ({@link #mergeAndRevive}) by re-publishing any * un-acked sub-messages back to the retry topic via {@link #reviveRetry}
      5. *
      * @@ -678,6 +678,7 @@ public void run() { int slow = 1; while (!this.isStopped()) { try { + // env check if (System.currentTimeMillis() < brokerController.getShouldStartTime()) { POP_LOGGER.info("PopReviveService Ready to run after {}", brokerController.getShouldStartTime()); this.waitForRunning(1000); @@ -695,6 +696,8 @@ public void run() { } POP_LOGGER.info("start revive topic={}, reviveQueueId={}", reviveTopic, queueId); + + // consume revive message ConsumeReviveObj consumeReviveObj = new ConsumeReviveObj(); consumeReviveMessage(consumeReviveObj); @@ -703,8 +706,10 @@ public void run() { continue; } + // merge checkpoint and ackMsg then revive mergeAndRevive(consumeReviveObj); + // wait and logging ArrayList sortList = consumeReviveObj.sortList; long delay = 0; if (sortList != null && !sortList.isEmpty()) { From cd64fa1a6e7a9726928b7648fe8b5f472fb3cf1e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 13:43:40 +0800 Subject: [PATCH 062/101] comment: add method comments to run of PopReviveService --- .../broker/processor/PopReviveService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 2c6faf77797..0969e41ae66 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -673,6 +673,21 @@ public long getReviveBehindMessages() throws ConsumeQueueException { return Math.max(0, diff); } + /** + * Main loop: periodically consume revive messages and revive timed-out CKs. + * + *

      Each iteration: + *

        + *
      1. Waits for {@code reviveInterval} (configurable)
      2. + *
      3. Calls {@link #consumeReviveMessage} to scan the revive topic and + * merge checkpoints with their corresponding AckMsg
      4. + *
      5. Calls {@link #mergeAndRevive} to re-publish all un-acked + * sub-messages whose revive time has elapsed
      6. + *
      7. If no checkpoints were processed, increases a {@code slow} counter and + * sleeps longer — the idle interval ramps up to + * {@code reviveMaxSlow * reviveInterval}
      8. + *
      + */ @Override public void run() { int slow = 1; From 72f7c3632d3dbeb39044cb3206ccfbcbc959b7f2 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 13:49:56 +0800 Subject: [PATCH 063/101] comment: add method comments to consumeReviveMessage of PopReviveService --- .../broker/processor/PopReviveService.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 0969e41ae66..bbe1217f634 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -352,6 +352,37 @@ private List decodeMsgList(GetMessageResult getMessageResult, boolea return foundList; } + /** + * Pull Message from revive topic then transfer to checkpoint and ack messages. + * + *

      This method reads messages from the revive topic starting from the + * current offset. Each message is classified by its tag: + *

        + *
      • {@link PopAckConstants#CK_TAG} — a checkpoint, deserialized from + * JSON and stored in the map by its merge key
      • + *
      • {@link PopAckConstants#ACK_TAG} or + * {@link PopAckConstants#BATCH_ACK_TAG} — an ack, matched to its + * corresponding checkpoint via the merge key. The ack offset is translated + * to a sub-message index ({@link PopCheckPoint#indexOfAck}) and + * the checkpoint's bitMap is updated via {@link DataConverter#setBit}
      • + *
      + * + *

      AckMsg that arrive after their checkpoint has already been processed + * ({@code enableSkipLongAwaitingAck}) are handled by creating a mock CK + * via {@link #mockCkForAck} so that the revive offset can still be + * committed correctly. + * + *

      The scan stops when any of: + *

        + *
      • No more messages in the revive topic (tail reached)
      • + *
      • Scan time exceeds {@code reviveScanTime}
      • + *
      • The elapsed time since the first CK's revive time exceeds + * {@code ackTimeInterval + 1s}
      • + *
      + * + * @param consumeReviveObj the mutable container that receives the collected + * CKs and the computed {@code endTime} + */ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { HashMap map = consumeReviveObj.map; HashMap mockPointMap = new HashMap<>(); From e85b23a5df33d8b540f1dd9958555a6a521bc3d5 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 13:55:17 +0800 Subject: [PATCH 064/101] comment: add in-line comments to method consumeReviveMessage of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index bbe1217f634..9fbcfd3bd5e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -384,6 +384,7 @@ private List decodeMsgList(GetMessageResult getMessageResult, boolea * CKs and the computed {@code endTime} */ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { + // init context parameters HashMap map = consumeReviveObj.map; HashMap mockPointMap = new HashMap<>(); long startScanTime = System.currentTimeMillis(); @@ -396,11 +397,14 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { int noMsgCount = 0; long firstRt = 0; // offset self amend + while (true) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip scan, revive topic={}, reviveQueueId={}", reviveTopic, queueId); break; } + + // pull revive messages List messageExts = getReviveMessage(offset, queueId); if (messageExts == null || messageExts.isEmpty()) { long old = endTime; @@ -429,10 +433,13 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { } else { noMsgCount = 0; } + if (System.currentTimeMillis() - startScanTime > brokerController.getBrokerConfig().getReviveScanTime()) { POP_LOGGER.info("reviveQueueId={}, scan timeout ", queueId); break; } + + // convert message to PopCheckPoint and AckMsg for (MessageExt messageExt : messageExts) { if (PopAckConstants.CK_TAG.equals(messageExt.getTags())) { String raw = new String(messageExt.getBody(), DataConverter.CHARSET_UTF8); From 6947ab3ae4137c77db336a79cfc99258077a0a26 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 14:02:53 +0800 Subject: [PATCH 065/101] comment: add method comments to getReviveMessage of PopReviveService --- .../rocketmq/broker/processor/PopReviveService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 9fbcfd3bd5e..5abdb30fd2a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -224,6 +224,17 @@ private int getRetryQueueId(String retryTopic, MessageExt messageExt) { return oriQueueId; } + /** + * Pull a batch of messages from the revive topic at the given offset. + * + *

      If the offset becomes illegal (e.g. the revive topic was truncated), + * the revive offset is corrected to {@code nextBeginOffset - 1} so that + * the next scan starts from a valid position. + * + * @param offset the queue offset to start reading from + * @param queueId the revive queue id + * @return a list of decoded messages, or {@code null} if at the tail + */ protected List getReviveMessage(long offset, int queueId) { PullResult pullResult = getMessage(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, offset, 32, true); if (pullResult == null) { From 4874a13a16d646c82429fad5e27f9542a41ff17b Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 14:57:21 +0800 Subject: [PATCH 066/101] comment: add in-line comments to method consumeReviveMessage of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 5abdb30fd2a..3b947c4f02a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -479,6 +479,7 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { String mergeKey = ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime() + brokerName; PopCheckPoint point = map.get(mergeKey); if (point == null) { + // default value of enableSkipLongAwaitingAck is false if (!brokerController.getBrokerConfig().isEnableSkipLongAwaitingAck()) { continue; } @@ -486,6 +487,7 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { firstRt = mockPointMap.get(mergeKey).getReviveTime(); } } else { + // merge ackMsg into checkpoint int indexOfAck = point.indexOfAck(ackMsg.getAckOffset()); if (indexOfAck > -1) { point.setBitMap(DataConverter.setBit(point.getBitMap(), indexOfAck, true)); @@ -506,6 +508,7 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { String mergeKey = bAckMsg.getTopic() + bAckMsg.getConsumerGroup() + bAckMsg.getQueueId() + bAckMsg.getStartOffset() + bAckMsg.getPopTime() + brokerName; PopCheckPoint point = map.get(mergeKey); if (point == null) { + // default value of enableSkipLongAwaitingAck is false if (!brokerController.getBrokerConfig().isEnableSkipLongAwaitingAck()) { continue; } @@ -513,6 +516,7 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { firstRt = mockPointMap.get(mergeKey).getReviveTime(); } } else { + // merge ackMsgs into checkpoint List ackOffsetList = bAckMsg.getAckOffsetList(); for (Long ackOffset : ackOffsetList) { int indexOfAck = point.indexOfAck(ackOffset); From a0246597ebb7b52d5e23fcf87cb814c324d975ed Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 15:01:08 +0800 Subject: [PATCH 067/101] comment: add method comments to mockCkForAck and createMockCkForAck of PopReviveService --- .../broker/processor/PopReviveService.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 3b947c4f02a..6795cf371cc 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -539,6 +539,20 @@ protected void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) { consumeReviveObj.endTime = endTime; } + /** + * Create a mock CK for an ack whose original CK has already been processed. + * + *

      When an ack arrives long after its CK has been consumed (e.g. network + * delay), the CK is no longer in the scan map. If {@code enableSkipLongAwaitingAck} + * is enabled, this method creates a synthetic CK so that the revive offset + * can still be advanced correctly in {@link #mergeAndRevive}. + * + * @param messageExt the revive topic message that carried the ack + * @param ackMsg the decoded ack + * @param mergeKey the merge key for the CK lookup + * @param mockPointMap map to collect the mock CKs + * @return {@code true} if a mock CK was created + */ private boolean mockCkForAck(MessageExt messageExt, AckMsg ackMsg, String mergeKey, HashMap mockPointMap) { long ackWaitTime = System.currentTimeMillis() - messageExt.getDeliverTimeMs(); long reviveAckWaitMs = brokerController.getBrokerConfig().getReviveAckWaitMs(); @@ -554,6 +568,17 @@ private boolean mockCkForAck(MessageExt messageExt, AckMsg ackMsg, String mergeK return false; } + /** + * Build a synthetic checkpoint from an ack message. + * + *

      The mock CK has {@code num = 0} and empty bitMap, meaning no actual + * messages to revive. Its only purpose is to carry the {@code reviveOffset} + * so that the revive consumer offset can be committed past this ack. + * + * @param ackMsg the ack message + * @param reviveOffset the queue offset of the ack message in the revive topic + * @return a mock checkpoint with no sub-messages + */ private PopCheckPoint createMockCkForAck(AckMsg ackMsg, long reviveOffset) { PopCheckPoint point = new PopCheckPoint(); point.setStartOffset(ackMsg.getStartOffset()); From 0e682f3b0a8ffce3821e243bd74dc00765327ea3 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 15:08:07 +0800 Subject: [PATCH 068/101] comment: add method comments to mergeAndRevive of PopReviveService --- .../broker/processor/PopReviveService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 6795cf371cc..0899b401bc8 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -593,6 +593,24 @@ private PopCheckPoint createMockCkForAck(AckMsg ackMsg, long reviveOffset) { return point; } + /** + * Process collected checkpoints and revive all un-acked sub-messages. + * + *

      Checkpoints are sorted by revive offset. For each one: + *

        + *
      • Skip if the revive time has not yet elapsed (within + * {@code ackTimeInterval + 1s} of {@code endTime})
      • + *
      • Skip if the normal topic or consumer group no longer exists
      • + *
      • Wait if too many revives are already in-flight (max 3)
      • + *
      • Call {@link #reviveMsgFromCk} to re-publish un-acked messages
      • + *
      + * + *

      After processing, the revive topic offset is advanced past all + * processed checkpoints. + * + * @param consumeReviveObj the container with collected CKs and scan state + * @throws Throwable if any revive operation fails + */ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwable { ArrayList sortList = consumeReviveObj.genSortList(); POP_LOGGER.info("reviveQueueId={}, ck listSize={}", queueId, sortList.size()); From 8fbe4225fd8ef293036539e878dd947f81151bf7 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 15:18:07 +0800 Subject: [PATCH 069/101] comment: add in-line comments to method mergeAndRevive of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 0899b401bc8..1720c3b63b7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -612,6 +612,7 @@ private PopCheckPoint createMockCkForAck(AckMsg ackMsg, long reviveOffset) { * @throws Throwable if any revive operation fails */ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwable { + // sort checkpoints and init newOffset ArrayList sortList = consumeReviveObj.genSortList(); POP_LOGGER.info("reviveQueueId={}, ck listSize={}", queueId, sortList.size()); if (sortList.size() != 0) { @@ -619,6 +620,7 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl sortList.get(0).getReviveOffset(), sortList.get(sortList.size() - 1).getStartOffset(), sortList.get(sortList.size() - 1).getReviveOffset()); } long newOffset = consumeReviveObj.oldOffset; + for (PopCheckPoint popCheckPoint : sortList) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip ck process, revive topic={}, reviveQueueId={}", reviveTopic, queueId); @@ -641,6 +643,7 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl continue; } + // restore checkpoint while (inflightReviveRequestMap.size() > 3) { waitForRunning(100); Pair pair = inflightReviveRequestMap.firstEntry().getValue(); @@ -653,10 +656,12 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl } } + // revive message reviveMsgFromCk(popCheckPoint); - newOffset = popCheckPoint.getReviveOffset(); } + + // commit offset if (newOffset > consumeReviveObj.oldOffset) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip commit, revive topic={}, reviveQueueId={}", reviveTopic, queueId); From f163d0daa8df16f9e2d714fc2343ff8c4a103d3d Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 15:23:55 +0800 Subject: [PATCH 070/101] comment: add method comments to rePutCK of PopReviveService --- .../broker/processor/PopReviveService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 1720c3b63b7..cf3428b4d36 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -725,6 +725,24 @@ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { }); } + /** + * Re-write a checkpoint to the revive topic after a failed revive attempt. + * + *

      When a sub-message cannot be revived (e.g. the original message is + * temporarily unavailable), the CK is re-published with: + *

        + *
      • A single sub-message targeting the failed offset
      • + *
      • An increased {@code rePutTimes} and an extended invisible time + * based on the backoff interval
      • + *
      • A cleared bitMap, so the next revive cycle will retry it
      • + *
      + * + *

      If {@code rePutTimes} exceeds the backoff table length and + * {@code skipWhenCKRePutReachMaxTimes} is set, the CK is dropped. + * + * @param oldCK the original checkpoint that failed to revive + * @param pair the failed offset and result (object1 = offset, object2 = result) + */ private void rePutCK(PopCheckPoint oldCK, Pair pair) { int rePutTimes = oldCK.parseRePutTimes(); if (rePutTimes >= ckRewriteIntervalsInSeconds.length && brokerController.getBrokerConfig().isSkipWhenCKRePutReachMaxTimes()) { From fe4ff189e7ed2976491182581134c79a39710ab5 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 19:14:21 +0800 Subject: [PATCH 071/101] comment: add method comments to reviveMsgFromCk of PopReviveService --- .../broker/processor/PopReviveService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index cf3428b4d36..578f3f90d28 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -673,6 +673,25 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl consumeReviveObj.newOffset = newOffset; } + /** + * Revive all un-acked sub-messages in a checkpoint: + * - reput message to revive topic + * - put message to retry topic + * + *

      For each sub-message whose bit is not set in the bitMap, the original + * message is fetched via {@link #getBizMessage} and re-published to the + * retry topic via {@link #reviveRetry}. All revive attempts run + * concurrently via {@link CompletableFuture#allOf}. + * + *

      After all attempts complete: + *

        + *
      • Failed offsets are re-queued via {@link #rePutCK}
      • + *
      • The {@link #inflightReviveRequestMap} is updated and completed + * entries are removed in order, advancing the revive offset
      • + *
      + * + * @param popCheckPoint the checkpoint whose un-acked messages should be revived + */ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip retry, revive topic={}, reviveQueueId={}", reviveTopic, queueId); From 4891cffc93c049cc97e761d5704a741bff0a45d7 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 19:52:34 +0800 Subject: [PATCH 072/101] comment: add in-line comments to method reviveMsgFromCk of PopReviveService --- .../rocketmq/broker/processor/PopReviveService.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 578f3f90d28..e20747b920c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -693,21 +693,26 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl * @param popCheckPoint the checkpoint whose un-acked messages should be revived */ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { + // env check and init if (!shouldRunPopRevive) { POP_LOGGER.info("slave skip retry, revive topic={}, reviveQueueId={}", reviveTopic, queueId); return; } inflightReviveRequestMap.put(popCheckPoint, new Pair<>(System.currentTimeMillis(), false)); List>> futureList = new ArrayList<>(popCheckPoint.getNum()); + + // put message to retry topic if checkpoint was not acked for (int j = 0; j < popCheckPoint.getNum(); j++) { + // if checkpoint was acked, skip if (DataConverter.getBit(popCheckPoint.getBitMap(), j)) { continue; } - // retry msg + // get message by checkpoint, then put message to retry topic long msgOffset = popCheckPoint.ackOffsetByIndex((byte) j); CompletableFuture> future = getBizMessage(popCheckPoint, msgOffset) .thenApply(rst -> { + // validate message MessageExt message = rst.getLeft(); if (message == null) { POP_LOGGER.info("reviveQueueId={}, can not get biz msg, topic:{}, qid:{}, offset:{}, brokerName:{}, info:{}, retry:{}, then continue", @@ -719,8 +724,11 @@ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { }); futureList.add(future); } + + // reput checkpoint to revive topic if retry failed CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((v, e) -> { + // reput checkpoint for (CompletableFuture> future : futureList) { Pair pair = future.getNow(new Pair<>(0L, false)); if (!pair.getObject2()) { @@ -728,9 +736,12 @@ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) { } } + // update ack status of inflight checkpoint if (inflightReviveRequestMap.containsKey(popCheckPoint)) { inflightReviveRequestMap.get(popCheckPoint).setObject2(true); } + + // commit offset and remove inflight checkpoint for (Map.Entry> entry : inflightReviveRequestMap.entrySet()) { PopCheckPoint oldCK = entry.getKey(); Pair pair = entry.getValue(); From 10a7fe81507b46e92b5a889d09344271a3c08484 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 21:47:33 +0800 Subject: [PATCH 073/101] comment: add concurrency control comments to method mergeAndRevive of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index e20747b920c..5f53ee9c8d7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -643,12 +643,14 @@ protected void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwabl continue; } - // restore checkpoint + // Concurrency control for revive: skip first long-running revive task. while (inflightReviveRequestMap.size() > 3) { waitForRunning(100); Pair pair = inflightReviveRequestMap.firstEntry().getValue(); + // if first revive task is timeout, reput it to revive topic, then skip if (!pair.getObject2() && System.currentTimeMillis() - pair.getObject1() > 1000 * 30) { PopCheckPoint oldCK = inflightReviveRequestMap.firstKey(); + // reput checkpoint to revive topic rePutCK(oldCK, pair); inflightReviveRequestMap.remove(oldCK); POP_LOGGER.warn("stay too long, remove from reviveRequestMap, {}, {}, {}, {}", popCheckPoint.getTopic(), From 614544453b75e1f2c2e044afced09ee4180a4fa5 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 21:51:06 +0800 Subject: [PATCH 074/101] comment: add attribute comments to inflightReviveRequestMap of PopReviveService --- .../broker/processor/PopReviveService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index 5f53ee9c8d7..bbaf35946ce 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -93,6 +93,25 @@ public class PopReviveService extends ServiceThread { private long currentReviveMessageTimestamp = -1; private volatile boolean shouldRunPopRevive = false; + /** + * Tracks checkpoints that are currently being revived. + * + *

      Key — the checkpoint being processed. + * Value — a pair of (startTime, completed), where: + *

        + *
      • {@code startTime} is the timestamp when revival began
      • + *
      • {@code completed} is {@code true} once all sub-messages have been + * processed (success or failure)
      • + *
      + * + *

      The map is sorted by {@link PopCheckPoint#compareTo} (by startOffset). + * This ordering is used to drain completed entries from the head, ensuring + * the revive topic offset is committed strictly in sequence. + * + *

      Concurrency is limited to at most 3 entries at a time (see + * {@link #mergeAndRevive}). If an entry stays incomplete for over 30 + * seconds, it is considered hung and is skipped via {@link #rePutCK}. + */ private final NavigableMap> inflightReviveRequestMap = Collections.synchronizedNavigableMap(new TreeMap<>()); private long reviveOffset; From f24cae00fc75a9c78f22e7dbf8e8117397573ef1 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 21:54:41 +0800 Subject: [PATCH 075/101] comment: add in-line comments to method reviveRetry of PopReviveService --- .../apache/rocketmq/broker/processor/PopReviveService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index bbaf35946ce..a39d4ffac64 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -143,6 +143,7 @@ public boolean isShouldRunPopRevive() { } private boolean reviveRetry(PopCheckPoint popCheckPoint, MessageExt messageExt) { + // convert checkpoint to inner message MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); if (!popCheckPoint.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { msgInner.setTopic(KeyBuilder.buildPopRetryTopic(popCheckPoint.getTopic(), popCheckPoint.getCId(), brokerController.getBrokerConfig().isEnableRetryTopicV2())); @@ -171,9 +172,15 @@ private boolean reviveRetry(PopCheckPoint popCheckPoint, MessageExt messageExt) } msgInner.getProperties().put(MessageConst.PROPERTY_ORIGIN_GROUP, popCheckPoint.getCId()); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + + // set topic and queueId addRetryTopicIfNotExist(msgInner.getTopic(), popCheckPoint.getCId()); msgInner.setQueueId(getRetryQueueId(msgInner.getTopic(), messageExt)); + + // store message PutMessageResult putMessageResult = brokerController.getEscapeBridge().putMessageToSpecificQueue(msgInner); + + // logging and metric brokerController.getBrokerMetricsManager().getPopMetricsManager().incPopReviveRetryMessageCount(popCheckPoint, putMessageResult.getPutMessageStatus()); if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("reviveQueueId={},retry msg, ck={}, msg queueId {}, offset {}, reviveDelay={}, result is {} ", From 241951de59e0363b03647869dbf4d687f4eb8fad Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 22:09:39 +0800 Subject: [PATCH 076/101] comment: add method comments to reviveRetry of PopReviveService --- .../broker/processor/PopReviveService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java index a39d4ffac64..a6048c39ed7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java @@ -142,6 +142,21 @@ public boolean isShouldRunPopRevive() { return shouldRunPopRevive; } + /** + * Re-publish a timed-out message to the retry topic. + * + *

      Constructs a new {@link MessageExtBrokerInner} from the original + * message, increments the reconsume count (unless suspended), sets the + * first-pop time and origin group properties, and writes it to the + * appropriate retry topic (V1 or V2 depending on configuration). + * + *

      If the retry topic does not exist, it is created automatically + * via {@link #addRetryTopicIfNotExist}. + * + * @param popCheckPoint the checkpoint that triggered the revive + * @param messageExt the original message to re-publish + * @return {@code true} if the message was written successfully + */ private boolean reviveRetry(PopCheckPoint popCheckPoint, MessageExt messageExt) { // convert checkpoint to inner message MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); From d51909c19e4afb3d2c4b4631e23e064e6820d4c8 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 22:38:24 +0800 Subject: [PATCH 077/101] comment: add class comments to AckMessageProcessor --- .../broker/processor/AckMessageProcessor.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index 34a790efca7..af33c747205 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -54,6 +54,27 @@ import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.BatchAckMsg; +/** + * Processes consumer ack messages in Pop consumption mode. + * + *

      Handles both single ({@link RequestCode#ACK_MESSAGE}) and batch + * ({@link RequestCode#BATCH_ACK_MESSAGE}) acks. Each ack is processed + * through one of two paths: + *

        + *
      • KVStore path ({@code popConsumerKVServiceEnable=true}) — + * delegates to {@link PopConsumerService#ackAsync}
      • + *
      • File-based path — tries {@link PopBufferMergeService#addAk} + * first; if the buffer merge is not available, writes the ack as a + * message to the system revive topic
      • + *
      + * + *

      Orderly ack is handled separately by {@link #ackOrderly} / + * {@link #ackOrderlyNew}, which update the consumer order info and advance + * the consumer offset while notifying any long-polling waiters. + * + *

      This class also owns and manages the {@link PopReviveService} instances + * for the file-based revive path. + */ public class AckMessageProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); From 7a35bb4ffd4381bbfb3e169f9a016cde2b784217 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Wed, 27 May 2026 22:40:23 +0800 Subject: [PATCH 078/101] comment: add class comments to ChangeInvisibleTimeProcessor --- .../ChangeInvisibleTimeProcessor.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index 5ff132ca237..b351fa85787 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -27,6 +27,7 @@ import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.offset.MemoryConsumerOrderInfoManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; +import org.apache.rocketmq.broker.pop.PopConsumerService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.PopAckConstants; import org.apache.rocketmq.common.TopicConfig; @@ -52,6 +53,23 @@ import org.apache.rocketmq.store.pop.AckMsg; import org.apache.rocketmq.store.pop.PopCheckPoint; +/** + * Processes the nack {@code ChangeInvisibleTime} request from consumers. + * + *

      When a consumer needs more time to process a message (or wants to + * suspend/nack it), this processor updates the message's visibility + * timeout. The implementation varies by the ack mode: + *

        + *
      • KVStore path — delegates to + * {@link PopConsumerService#changeInvisibilityDuration}
      • + *
      • File-based path — writes a new CK to the revive topic with + * the updated invisible time, then acks the original CK so that + * the message will not be revived until the new timeout expires
      • + *
      + * + *

      For orderly consumption, the next visible time is updated directly in + * the {@link ConsumerOrderInfoManager} without writing to the revive topic. + */ public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); private final BrokerController brokerController; From 3efe3ba1c7fde7631970a53e92b0eeb6ee767d2e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:09:18 +0800 Subject: [PATCH 079/101] comment: add note about default mode in PopMessageProcessor --- .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index d260951ea59..20c49ee8c22 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -388,7 +388,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC // There are two type of ack mode: // 1. ack by KV service - // 2. ack by file merge service + // 2. ack by file merge service, default mode if (brokerConfig.isPopConsumerKVServiceEnable()) { CompletableFuture popAsyncFuture = brokerController.getPopConsumerService().popAsync( From 76495cff3619f9ccd874c981b2a05a43f12d6996 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:14:59 +0800 Subject: [PATCH 080/101] comment: add in-line comments to method processRequest of AckMessageProcessor --- .../broker/processor/AckMessageProcessor.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index af33c747205..6a508f6e525 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -26,6 +26,7 @@ import org.apache.rocketmq.broker.lite.LiteMetadataUtil; import org.apache.rocketmq.broker.offset.ConsumerOffsetManager; import org.apache.rocketmq.broker.pop.PopConsumerLockService; +import org.apache.rocketmq.broker.pop.PopConsumerService; import org.apache.rocketmq.broker.pop.orderly.ConsumerOrderInfoManager; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.PopAckConstants; @@ -144,11 +145,14 @@ public boolean rejectRequest() { private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { + // init context params AckMessageRequestHeader requestHeader; BatchAckMessageRequestBody reqBody = null; final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); response.setOpaque(request.getOpaque()); + if (request.getCode() == RequestCode.ACK_MESSAGE) { + // decode and validate request requestHeader = (AckMessageRequestHeader) request.decodeCommandCustomHeader(AckMessageRequestHeader.class); TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); @@ -188,12 +192,15 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re response.setRemark(errorInfo); return response; } + + // append ack if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { appendAckNew(requestHeader, null, response, channel, null); } else { appendAck(requestHeader, null, response, channel, null); } } else if (request.getCode() == RequestCode.BATCH_ACK_MESSAGE) { + // decode and validate request if (request.getBody() != null) { reqBody = BatchAckMessageRequestBody.decode(request.getBody(), BatchAckMessageRequestBody.class); } @@ -201,7 +208,10 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re response.setCode(ResponseCode.NO_MESSAGE); return response; } + + // process each ack for (BatchAck bAck : reqBody.getAcks()) { + // default value of popConsumerKVServiceEnable is false if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { appendAckNew(null, bAck, response, channel, reqBody.getBrokerName()); } else { @@ -209,6 +219,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re } } } else { + // unsupported request, logging and return POP_LOGGER.error("AckMessageProcessor failed to process RequestCode: {}, consumer: {} ", request.getCode(), RemotingHelper.parseChannelRemoteAddr(channel)); response.setCode(ResponseCode.MESSAGE_ILLEGAL); response.setRemark(String.format("AckMessageProcessor failed to process RequestCode: %d", request.getCode())); From 0485831764885b034d412c447e7eaf5cd1801f2a Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:17:11 +0800 Subject: [PATCH 081/101] comment: add method comments to processRequest of AckMessageProcessor --- .../broker/processor/AckMessageProcessor.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index 6a508f6e525..a38b95a6158 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -143,6 +143,26 @@ public boolean rejectRequest() { return false; } + /** + * Process an ack request (single or batch). + * + *

      Routes to one of two paths based on {@code popConsumerKVServiceEnable}: + *

        + *
      • {@code true} — {@link #appendAckNew} (KVStore path, delegates to + * {@link PopConsumerService#ackAsync})
      • + *
      • {@code false} — {@link #appendAck} (file-based path, tries + * {@link PopBufferMergeService#addAk} first, then writes to revive topic)
      • + *
      + * + *

      Orderly acks ({@code rqId == POP_ORDER_REVIVE_QUEUE}) are handled by + * {@link #ackOrderly} / {@link #ackOrderlyNew} instead. + * + * @param channel the Netty channel of the requesting client + * @param request the incoming request + * @param brokerAllowSuspend whether the broker may suspend the request + * @return the response to send back to the client + * @throws RemotingCommandException if the request cannot be decoded + */ private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { // init context params From 7927a88518874c5a3fbad8a1b78455d0075e7ea0 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:21:07 +0800 Subject: [PATCH 082/101] comment: add in-line comments to method processRequest of ChangeInvisibleTimeProcessor --- .../broker/processor/ChangeInvisibleTimeProcessor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index b351fa85787..889ffe3d286 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -94,8 +94,12 @@ public boolean rejectRequest() { private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { + // process request async CompletableFuture responseFuture = processRequestAsync(channel, request, brokerAllowSuspend); + // process response sync or a sync + // default value of appendCkAsync is false + // default value of appendAckAsync is false if (brokerController.getBrokerConfig().isAppendCkAsync() && brokerController.getBrokerConfig().isAppendAckAsync()) { responseFuture.thenAccept(response -> doResponse(channel, request, response)).exceptionally(throwable -> { RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); From 0d5ebc9ef057535d088f63f57635ece98f33c3ed Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:26:38 +0800 Subject: [PATCH 083/101] comment: add in-line comments to method processRequestAsync of ChangeInvisibleTimeProcessor --- .../broker/processor/ChangeInvisibleTimeProcessor.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index 889ffe3d286..bff34264e19 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -126,6 +126,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re public CompletableFuture processRequestAsync(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { + // decode and validate request final ChangeInvisibleTimeRequestHeader requestHeader = (ChangeInvisibleTimeRequestHeader) request.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class); RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); response.setCode(ResponseCode.SUCCESS); @@ -148,11 +149,13 @@ public CompletableFuture processRequestAsync(final Channel chan return CompletableFuture.completedFuture(response); } + // lite topic process CompletableFuture future = processChangeInvisibleTimeForLite(requestHeader, response, responseHeader); if (future != null) { return future; } + // offset check long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId()); long maxOffset; try { @@ -166,6 +169,9 @@ public CompletableFuture processRequestAsync(final Channel chan } String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); + + // default value of popConsumerKVServiceEnable is false + // kv based ack service if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { if (ExtraInfoUtil.isOrder(extraInfo)) { return this.processChangeInvisibleTimeForOrderNew( @@ -186,6 +192,9 @@ public CompletableFuture processRequestAsync(final Channel chan return CompletableFuture.completedFuture(response); } + // file merge based ack service + + // orderly topic if (ExtraInfoUtil.isOrder(extraInfo)) { return CompletableFuture.completedFuture( processChangeInvisibleTimeForOrder(requestHeader, extraInfo, response, responseHeader)); @@ -196,6 +205,7 @@ public CompletableFuture processRequestAsync(final Channel chan CompletableFuture futureResult = appendCheckPointThenAckOrigin(requestHeader, ExtraInfoUtil.getReviveQid(extraInfo), requestHeader.getQueueId(), requestHeader.getOffset(), now, extraInfo); + // format response return futureResult.thenCompose(result -> { if (result) { responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); From 7b762e02b52dfc4e5279dee2f0db7c372863db58 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:33:22 +0800 Subject: [PATCH 084/101] comment: add in-line comments to method appendCheckPointThenAckOrigin of ChangeInvisibleTimeProcessor --- .../broker/processor/ChangeInvisibleTimeProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index bff34264e19..2dfa4c9d38c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -346,6 +346,8 @@ private CompletableFuture appendCheckPointThenAckOrigin( // add check point msg to revive log MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); + + // create checkpoint PopCheckPoint ck = new PopCheckPoint(); ck.setBitMap(0); ck.setNum((byte) 1); @@ -359,6 +361,7 @@ private CompletableFuture appendCheckPointThenAckOrigin( ck.setBrokerName(ExtraInfoUtil.getBrokerName(extraInfo)); ck.setSuspend(requestHeader.isSuspend()); + // init message with checkpoint msgInner.setBody(JSON.toJSONString(ck).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(reviveQid); msgInner.setTags(PopAckConstants.CK_TAG); @@ -368,6 +371,8 @@ private CompletableFuture appendCheckPointThenAckOrigin( msgInner.setDeliverTimeMs(ck.getReviveTime() - PopAckConstants.ackTimeInterval); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genCkUniqueId(ck)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + + // store message then call ackOrigin return this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenCompose(putMessageResult -> { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("change Invisible, appendCheckPoint, topic {}, queueId {},reviveId {}, cid {}, startOffset {}, rt {}, result {}", requestHeader.getTopic(), queueId, reviveQid, requestHeader.getConsumerGroup(), offset, @@ -381,6 +386,7 @@ private CompletableFuture appendCheckPointThenAckOrigin( this.brokerController.getBrokerStatsManager().incGroupCkNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); } } + if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT From 328195d49af217358fe141cf4a30dfb99f0934dc Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 12:36:25 +0800 Subject: [PATCH 085/101] comment: add in-line comments to method ackOrigin of ChangeInvisibleTimeProcessor --- .../broker/processor/ChangeInvisibleTimeProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index 2dfa4c9d38c..44840e256a1 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -294,6 +294,7 @@ protected RemotingCommand processChangeInvisibleTimeForOrder(ChangeInvisibleTime private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo) { + // create ackMsg and related message MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); AckMsg ackMsg = new AckMsg(); @@ -310,10 +311,12 @@ private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHea this.brokerController.getBrokerStatsManager().incBrokerAckNums(1); this.brokerController.getBrokerStatsManager().incGroupAckNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(), 1); + // add ackMsg if (brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) { return CompletableFuture.completedFuture(true); } + // init message msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(StandardCharsets.UTF_8)); msgInner.setQueueId(rqId); @@ -324,6 +327,8 @@ private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHea msgInner.setDeliverTimeMs(ExtraInfoUtil.getPopTime(extraInfo) + ExtraInfoUtil.getInvisibleTime(extraInfo)); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + + // store message return this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenCompose(putMessageResult -> { if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT From aa04f66e976d32fa98ab2051e5255546292f70ce Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:07:06 +0800 Subject: [PATCH 086/101] comment: update inline comment for appendCheckPointThenAckOrigin --- .../broker/processor/ChangeInvisibleTimeProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index 44840e256a1..b28513aff75 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -377,7 +377,8 @@ private CompletableFuture appendCheckPointThenAckOrigin( msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genCkUniqueId(ck)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - // store message then call ackOrigin + // store new checkpoint to extend invisible time + // then ack origin checkpoint return this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenCompose(putMessageResult -> { if (brokerController.getBrokerConfig().isEnablePopLog()) { POP_LOGGER.info("change Invisible, appendCheckPoint, topic {}, queueId {},reviveId {}, cid {}, startOffset {}, rt {}, result {}", requestHeader.getTopic(), queueId, reviveQid, requestHeader.getConsumerGroup(), offset, From b72eb4e8dd3c45d768d9f068099a36c6fa5287a6 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:14:52 +0800 Subject: [PATCH 087/101] comment: add method comments to processRequestAsync of ChangeInvisibleTimeProcessor --- .../ChangeInvisibleTimeProcessor.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index b28513aff75..364e850df95 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -124,6 +124,24 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re return null; } + /** + * Asynchronously process a ChangeInvisibleTime request. + * + *

      Routes to the appropriate handler based on message type: + *

        + *
      • Lite message — {@link #processChangeInvisibleTimeForLite}
      • + *
      • KVStore path + orderly — {@link #processChangeInvisibleTimeForOrderNew}
      • + *
      • KVStore path + non-orderly — {@link PopConsumerService#changeInvisibilityDuration}
      • + *
      • File-based path + orderly — {@link #processChangeInvisibleTimeForOrder}
      • + *
      • File-based path + non-orderly — {@link #appendCheckPointThenAckOrigin}
      • + *
      + * + * @param channel the Netty channel + * @param request the incoming request + * @param brokerAllowSuspend whether the broker may suspend + * @return a future that completes with the response + * @throws RemotingCommandException if the request cannot be decoded + */ public CompletableFuture processRequestAsync(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { // decode and validate request @@ -200,7 +218,7 @@ public CompletableFuture processRequestAsync(final Channel chan processChangeInvisibleTimeForOrder(requestHeader, extraInfo, response, responseHeader)); } - // add new ck + // add new checkpoint then ack origin checkpoint long now = System.currentTimeMillis(); CompletableFuture futureResult = appendCheckPointThenAckOrigin(requestHeader, ExtraInfoUtil.getReviveQid(extraInfo), requestHeader.getQueueId(), requestHeader.getOffset(), now, extraInfo); From 37c4ae2905800f112b202012c73602783c4c367c Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:18:33 +0800 Subject: [PATCH 088/101] comment: add method comments to appendCheckPointThenAckOrigin of ChangeInvisibleTimeProcessor --- .../ChangeInvisibleTimeProcessor.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index 364e850df95..d90252cea69 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -362,6 +362,27 @@ private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHea }); } + /** + * Extend the visibility timeout by writing a new checkpoint and ack the old one. + * + *

      This is the core of the file-based non-orderly ChangeInvisibleTime path: + *

        + *
      1. Writes a new CK ({@link PopAckConstants#CK_TAG}) to the revive + * topic with the updated {@code invisibleTime}. This CK will trigger a + * revive at the new timeout if not acked.
      2. + *
      3. If the CK is stored successfully, calls {@link #ackOrigin} to write + * an Ack ({@link PopAckConstants#ACK_TAG}) for the original CK, + * preventing the old CK from triggering a premature revive.
      4. + *
      + * + * @param requestHeader the original request header + * @param reviveQid the revive queue to write to + * @param queueId the original queue id + * @param offset the message offset being extended + * @param popTime the new pop time (current time) + * @param extraInfo the extra info from the original pop request + * @return a future that completes with {@code true} on success + */ private CompletableFuture appendCheckPointThenAckOrigin( final ChangeInvisibleTimeRequestHeader requestHeader, int reviveQid, @@ -411,6 +432,7 @@ private CompletableFuture appendCheckPointThenAckOrigin( } } + // if success, ack origin checkpoint if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT From 92cf992309b47b9b6c0c9b48166d1e95332bdde7 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:20:55 +0800 Subject: [PATCH 089/101] comment: add method comments to ackOrigin of ChangeInvisibleTimeProcessor --- .../processor/ChangeInvisibleTimeProcessor.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java index d90252cea69..5de0c3eac78 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java @@ -310,6 +310,22 @@ protected RemotingCommand processChangeInvisibleTimeForOrder(ChangeInvisibleTime return response; } + /** + * Ack the original checkpoint after created a new checkpoint successfully. + * + *

      Called after the new checkpoint has been written successfully. This method + * writes an {@link PopAckConstants#ACK_TAG} message that matches the + * original checkpoint's merge key. When {@link PopReviveService} processes this + * ack, it sets the corresponding bit in the old CK's bitMap, causing + * the old CK to be treated as fully acked and skipped during revive. + * + *

      If {@link PopBufferMergeService#addAk} accepts the ack (buffer + * merge enabled), it is merged in memory without writing to the store. + * + * @param requestHeader the original request header + * @param extraInfo the extra info from the original pop request + * @return a future that completes with {@code true} on success + */ private CompletableFuture ackOrigin(final ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo) { // create ackMsg and related message From 97d68c8abf1ce362ff2c002a160efdf608bdbadf Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:38:49 +0800 Subject: [PATCH 090/101] comment: update inline comment for ack mode in AckMessageProcessor --- .../apache/rocketmq/broker/processor/AckMessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index a38b95a6158..c24f7299284 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -213,7 +213,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re return response; } - // append ack + // append ack, default mode is file based merge, call appendAck if (brokerController.getBrokerConfig().isPopConsumerKVServiceEnable()) { appendAckNew(requestHeader, null, response, channel, null); } else { From fe753cb6e401d7947299b9422ecf6c2b72c53043 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:56:10 +0800 Subject: [PATCH 091/101] comment: add in-line comments to method appendAck of AckMessageProcessor --- .../broker/processor/AckMessageProcessor.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index c24f7299284..2d28c05fbfc 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -250,6 +250,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re private void appendAck(final AckMessageRequestHeader requestHeader, final BatchAck batchAck, final RemotingCommand response, final Channel channel, String brokerName) throws RemotingCommandException { + // init context params String[] extraInfo; String consumeGroup, topic; int qId, rqId; @@ -257,8 +258,11 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA long popTime, invisibleTime; AckMsg ackMsg; int ackCount = 0; + + // ack orderly or set context params if (batchAck == null) { // single ack + // set context params extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo()); brokerName = ExtraInfoUtil.getBrokerName(extraInfo); consumeGroup = requestHeader.getConsumerGroup(); @@ -270,15 +274,18 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA popTime = ExtraInfoUtil.getPopTime(extraInfo); invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfo); + // ack orderly if revive queue if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) { ackOrderly(topic, consumeGroup, qId, ackOffset, popTime, invisibleTime, channel, response); return; } + // set ackMsg and ackCount ackMsg = new AckMsg(); ackCount = 1; } else { // batch ack + // set context params consumeGroup = batchAck.getConsumerGroup(); topic = ExtraInfoUtil.getRealTopic(batchAck.getTopic(), batchAck.getConsumerGroup(), batchAck.getRetry()); qId = batchAck.getQueueId(); @@ -288,6 +295,7 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA popTime = batchAck.getPopTime(); invisibleTime = batchAck.getInvisibleTime(); + // offset check long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, qId); long maxOffset; try { @@ -300,6 +308,7 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA return; } + // ack orderly or add offset to batchAckMsg BatchAckMsg batchAckMsg = new BatchAckMsg(); BitSet bitSet = batchAck.getBitSet(); for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { @@ -316,10 +325,13 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA batchAckMsg.getAckOffsetList().add(offset); } } + + // skip if empty or is revive queue if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE || batchAckMsg.getAckOffsetList().isEmpty()) { return; } + // set ackMsg and ackCount ackMsg = batchAckMsg; ackCount = batchAckMsg.getAckOffsetList().size(); } @@ -327,6 +339,7 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA this.brokerController.getBrokerStatsManager().incBrokerAckNums(ackCount); this.brokerController.getBrokerStatsManager().incGroupAckNums(consumeGroup, topic, ackCount); + // set ackMsg ackMsg.setConsumerGroup(consumeGroup); ackMsg.setTopic(topic); ackMsg.setQueueId(qId); @@ -335,11 +348,13 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA ackMsg.setPopTime(popTime); ackMsg.setBrokerName(brokerName); + // add ackMsg if (this.brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) { brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount); return; } + // create revive message by ackMsg, if add ackMsg failed MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); msgInner.setTopic(reviveTopic); msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(StandardCharsets.UTF_8)); @@ -357,7 +372,9 @@ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchA msgInner.setDeliverTimeMs(popTime + invisibleTime); msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg)); msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); - if (brokerController.getBrokerConfig().isAppendAckAsync()) { + + // store revive message + if (brokerController.getBrokerConfig().isAppendAckAsync()) { // default is false int finalAckCount = ackCount; this.brokerController.getEscapeBridge().asyncPutMessageToSpecificQueue(msgInner).thenAccept(putMessageResult -> { handlePutMessageResult(putMessageResult, ackMsg, topic, consumeGroup, popTime, qId, finalAckCount); From f92c4e83b7e4447cc22d6700ba3d4b1997efd9f8 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 15:57:28 +0800 Subject: [PATCH 092/101] comment: add method comments to appendAck of AckMessageProcessor --- .../broker/processor/AckMessageProcessor.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java index 2d28c05fbfc..7e044c38db3 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java @@ -248,6 +248,28 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re return response; } + /** + * Append an ack (single or batch) in the file-based path. + * + *

      For single ack: parses the extra info from the request header, + * routes orderly acks to {@link #ackOrderly}, or creates a single {@link AckMsg}. + * + *

      For batch ack: expands the {@link BitSet} from the + * {@link BatchAck} into individual offsets, routes orderly acks individually, + * and packs the remaining offsets into a {@link BatchAckMsg}. + * + *

      The ack is first offered to {@link PopBufferMergeService#addAk}. + * If the buffer merge is not available, the ack is serialized as JSON and + * written to the revive topic with tag {@link PopAckConstants#ACK_TAG} + * or {@link PopAckConstants#BATCH_ACK_TAG}. + * + * @param requestHeader the single-ack request header (null for batch) + * @param batchAck the batch ack body (null for single) + * @param response the response to modify on error + * @param channel the Netty channel + * @param brokerName the broker name + * @throws RemotingCommandException if offset validation fails + */ private void appendAck(final AckMessageRequestHeader requestHeader, final BatchAck batchAck, final RemotingCommand response, final Channel channel, String brokerName) throws RemotingCommandException { // init context params From d5f43487d3a612388d09db8ce4b9b17ccfa3b9a6 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 16:12:49 +0800 Subject: [PATCH 093/101] comment: add class comments to PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 20c49ee8c22..fa49ec211e0 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -33,6 +33,7 @@ import org.apache.rocketmq.broker.longpolling.PopRequest; import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer; import org.apache.rocketmq.broker.pop.PopConsumerContext; +import org.apache.rocketmq.broker.pop.PopConsumerService; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.KeyBuilder; import org.apache.rocketmq.common.MixAll; @@ -99,6 +100,24 @@ import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESPONSE_CODE; import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_RESULT; +/** + * Processes PopMessage requests from consumers. + * + *

      This is the core processor for the Pop consumption mode. It handles: + *

        + *
      • Validating the request (topic, group, queue, subscription, permissions)
      • + *
      • Routing to the {@link PopConsumerService} (KVStore path) or the + * inline file-based path
      • + *
      • Popping messages from normal and retry topics (V1/V2)
      • + *
      • Creating checkpoints and writing them to the revive topic
      • + *
      • Long-polling suspension via {@link PopLongPollingService}
      • + *
      • Transferring messages to the client (heap copy or zero-copy)
      • + *
      + * + *

      This class also owns the {@link PopLongPollingService}, + * {@link PopBufferMergeService}, and {@link QueueLockManager} instances + * used by the file-based ack path. + */ public class PopMessageProcessor implements NettyRequestProcessor { private static final Logger POP_LOGGER = LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME); @@ -615,6 +634,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } + // format response responseHeader.setInvisibleTime(requestHeader.getInvisibleTime()); responseHeader.setPopTime(popTime); responseHeader.setReviveQid(reviveQid); From 72698c1da4daecab0f03c279644089d82e847c7a Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 16:15:22 +0800 Subject: [PATCH 094/101] comment: add method comments to processRequest of PopMessageProcessor --- .../broker/processor/PopMessageProcessor.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index fa49ec211e0..299579b70ac 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -241,6 +241,27 @@ public void notifyMessageArriving(final String topic, final int queueId, final S topic, queueId, cid, false, null, 0L, null, null); } + /** + * Process a PopMessage request. + * + *

      This method handles the full Pop lifecycle: + *

        + *
      1. Validates the request (topic, group, permissions, subscription)
      2. + *
      3. Routes to the KVStore path (via {@link PopConsumerService#popAsync}) + * or the file-based path (inline CompletableFuture chain)
      4. + *
      5. Pops messages from normal and retry topics (V1/V2)
      6. + *
      7. Creates checkpoints and appends them to the revive topic
      8. + *
      9. Suspends the request via {@link PopLongPollingService#polling} if + * no messages are available
      10. + *
      11. Transfers messages via heap copy or zero-copy ({@code FileRegion})
      12. + *
      + * + * @param ctx the Netty channel handler context + * @param request the incoming PopMessage request + * @return the response, or {@code null} if the response is sent asynchronously + * (zero-copy path or long-polling suspension) + * @throws RemotingCommandException if the request cannot be decoded + */ @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { From 24d2877fff3f7cf506c9cde86cb1590ddd3d9037 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 16:19:22 +0800 Subject: [PATCH 095/101] comment: add inline comments for kv path in PopMessageProcessor --- .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 299579b70ac..1b27a1b546b 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -438,6 +438,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC requestHeader.getAttemptId(), requestHeader.getInitMode(), messageFilter); popAsyncFuture.thenApply(result -> { + // callback try { if (request.getCallbackList() != null) { request.getCallbackList().forEach(CommandCallback::accept); @@ -447,6 +448,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC POP_LOGGER.error("PopProcessor execute callback error", t); } + // long polling process if (result.isFound()) { response.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); @@ -479,6 +481,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE); } + // format response responseHeader.setPopTime(result.getPopTime()); responseHeader.setInvisibleTime(result.getInvisibleTime()); responseHeader.setReviveQid( From 318426e795da29c8f5ed9a1dba5a8fcb3ceba67b Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 18:04:17 +0800 Subject: [PATCH 096/101] comment: update inline comment for long polling in PopMessageProcessor --- .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java index 1b27a1b546b..e863ae7ac79 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java @@ -448,7 +448,7 @@ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingC POP_LOGGER.error("PopProcessor execute callback error", t); } - // long polling process + // long polling process, useless in rocketmq 5.* if (result.isFound()) { response.setCode(ResponseCode.SUCCESS); getMessageResult.setStatus(GetMessageStatus.FOUND); From ed4aa415cef7f06abe00d83dccbe7b861e860fd7 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 18:31:02 +0800 Subject: [PATCH 097/101] comment: add in-line comments to method popAsync of PopConsumerService --- .../apache/rocketmq/broker/pop/PopConsumerService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java index 9ab5eb651be..d28cc513b7c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java @@ -356,6 +356,7 @@ public CompletableFuture popAsync(String clientHost, long po String groupId, String topicId, int queueId, int batchSize, boolean fifo, String attemptId, int initMode, MessageFilter filter) { + // init context params PopConsumerContext popConsumerContext = new PopConsumerContext(clientHost, popTime, invisibleTime, groupId, fifo, initMode, attemptId); @@ -391,18 +392,22 @@ public CompletableFuture popAsync(String clientHost, long po CompletableFuture.completedFuture(popConsumerContext); try { + // get message from retry topic, if (!fifo && preferRetry) { + // default config of retrieveMessageFromPopRetryTopicV1 is true, if (brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV1, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V1); } + // default config of enableRetryTopicV2 is false if (brokerConfig.isEnableRetryTopicV2()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, retryTopicV2, requestCount, batchSize, filter, PopConsumerRecord.RetryType.RETRY_TOPIC_V2); } } + // get message from normal topic if (queueId != -1) { getMessageFuture = this.getMessageAsync(getMessageFuture, clientHost, groupId, topicId, queueId, batchSize, filter, PopConsumerRecord.RetryType.NORMAL_TOPIC); @@ -410,6 +415,7 @@ public CompletableFuture popAsync(String clientHost, long po getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, topicId, requestCount, batchSize, filter, PopConsumerRecord.RetryType.NORMAL_TOPIC); + // get message from retry topic if (!fifo && !preferRetry) { if (brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) { getMessageFuture = this.getMessageFromTopicAsync(getMessageFuture, clientHost, groupId, @@ -425,6 +431,8 @@ public CompletableFuture popAsync(String clientHost, long po return getMessageFuture.thenCompose(result -> { if (result.isFound() && !result.isFifo()) { + // write checkpoint to cache or store + // default config of enablePopBufferMerge is false if (brokerConfig.isEnablePopBufferMerge() && popConsumerCache != null && !popConsumerCache.isCacheFull()) { this.popConsumerCache.writeRecords(result.getPopConsumerRecordList()); @@ -432,6 +440,7 @@ public CompletableFuture popAsync(String clientHost, long po this.popConsumerStore.writeRecords(result.getPopConsumerRecordList()); } + // format result for (int i = 0; i < result.getGetMessageResultList().size(); i++) { GetMessageResult getMessageResult = result.getGetMessageResultList().get(i); PopConsumerRecord popConsumerRecord = result.getPopConsumerRecordList().get(i); @@ -449,6 +458,7 @@ public CompletableFuture popAsync(String clientHost, long po } return CompletableFuture.completedFuture(result); }).whenComplete((result, throwable) -> { + // unlock by consumerLockService try { if (throwable != null) { log.error("PopConsumerService popAsync get message error", From de70be6855765607f5e3881b47e3154bda038d4e Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 18:34:27 +0800 Subject: [PATCH 098/101] comment: add method comments to popAsync of PopConsumerService --- .../broker/pop/PopConsumerService.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java index d28cc513b7c..eac85a28e4f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java @@ -352,6 +352,33 @@ protected CompletableFuture getMessageFromTopicAsync(Complet return future; } + /** + * Asynchronously pop messages for the KVStore-based ack path. + * + *

      This method coordinates the full Pop lifecycle: + *

        + *
      1. Validates topic, group, and acquires the consumer lock
      2. + *
      3. Determines whether to pull from retry topic first + * (based on {@code popFromRetryProbability})
      4. + *
      5. Pulls messages from normal topic (and retry topic V1/V2 if configured)
      6. + *
      7. Writes checkpoints to {@link PopConsumerCache} (buffer merge) or + * {@link PopConsumerKVStore} (RocksDB)
      8. + *
      9. Re-encodes retry messages if needed
      10. + *
      + * + * @param clientHost the client address + * @param popTime the pop invocation timestamp + * @param invisibleTime the message visibility timeout + * @param groupId consumer group id + * @param topicId topic name + * @param queueId queue id (-1 for all queues) + * @param batchSize max number of messages to return + * @param fifo whether this is a FIFO ordered consumption + * @param attemptId attempt id for idempotent consumption + * @param initMode consume init mode (min/max) + * @param filter message filter expression + * @return a future that completes with the pop result context + */ public CompletableFuture popAsync(String clientHost, long popTime, long invisibleTime, String groupId, String topicId, int queueId, int batchSize, boolean fifo, String attemptId, int initMode, MessageFilter filter) { From 31b092be3fafdeae2b278d080e9f69e66854ed08 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 18:43:26 +0800 Subject: [PATCH 099/101] comment: add method comments to getMessageFromTopicAsync of PopConsumerService --- .../broker/pop/PopConsumerService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java index eac85a28e4f..a619890fc36 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java @@ -335,6 +335,28 @@ protected CompletableFuture getMessageAsync(CompletableFutur }); } + /** + * Fetch messages from every read queue of a topic via a CompletableFuture chain. + * + *

      Each queue is visited once. For each queue the + * {@link #getMessageAsync(CompletableFuture, String, String, String, int, int, MessageFilter, PopConsumerRecord.RetryType)} + * method is chained via {@link CompletableFuture#thenCompose}. The chain carries + * the accumulated result through all queues, stopping early when the batch is + * filled, the queue is blocked, or the inflight threshold is reached. + * + *

      Queue iteration order respects {@code priorityOrderAsc} and uses + * {@code requestCount} as a round-robin offset for load balancing. + * + * @param future the accumulator future + * @param clientHost the client address + * @param groupId consumer group id + * @param topicId topic name + * @param requestCount round-robin counter for queue selection + * @param batchSize max number of messages to return + * @param filter message filter expression + * @param retryType whether this is a retry topic V1/V2 + * @return a future completing with the pop result context + */ protected CompletableFuture getMessageFromTopicAsync(CompletableFuture future, String clientHost, String groupId, String topicId, long requestCount, int batchSize, MessageFilter filter, PopConsumerRecord.RetryType retryType) { From 353f71b713066fd7e6ec7eb8b85fe0a94576e300 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 18:44:22 +0800 Subject: [PATCH 100/101] comment: add in-line comments to method getMessageFromTopicAsync of PopConsumerService --- .../org/apache/rocketmq/broker/pop/PopConsumerService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java index a619890fc36..d28f9be2583 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java @@ -360,10 +360,13 @@ protected CompletableFuture getMessageAsync(CompletableFutur protected CompletableFuture getMessageFromTopicAsync(CompletableFuture future, String clientHost, String groupId, String topicId, long requestCount, int batchSize, MessageFilter filter, PopConsumerRecord.RetryType retryType) { + // get topic config TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topicId); if (null == topicConfig) { return future; } + + // iterate all queues of the topic for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { long index = (brokerController.getBrokerConfig().isPriorityOrderAsc() ? topicConfig.getReadQueueNums() - 1 - i : i) + requestCount; From d2dc76431e2f5d40296998263a8c2ba3f7e60752 Mon Sep 17 00:00:00 2001 From: "winglechen@aliyun.com" Date: Thu, 28 May 2026 19:28:16 +0800 Subject: [PATCH 101/101] comment: add method comments to getMessageAsync of PopConsumerService --- .../broker/pop/PopConsumerService.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java index d28f9be2583..e3f4f1e21c6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java @@ -298,6 +298,31 @@ public boolean isFifoBlocked(PopConsumerContext context, String groupId, String context.getAttemptId(), topicId, groupId, queueId, context.getInvisibleTime()); } + /** + * Fetch messages from a single queue and append them to the pop context. + * + *

      Chained via {@link CompletableFuture#thenCompose} from + * {@link #getMessageFromTopicAsync}. When the batch is already full + * ({@code remain <= 0}), the pending count is added to the context and + * the chain stops. Otherwise, messages are fetched from the store and + * the result is merged into the context via {@link #handleGetMessageResult}. + * + *

      Early termination can occur inside this method when: + *

        + *
      • Too many inflight (un-acked) messages exist
      • + *
      • A FIFO queue is blocked
      • + *
      + * + * @param future the accumulator future carrying the pop context + * @param clientHost the client address + * @param groupId consumer group id + * @param topicId topic name + * @param queueId queue id + * @param batchSize max number of messages still needed + * @param filter message filter + * @param retryType whether this is a retry topic V1/V2 + * @return a future completing with the pop context updated with results + */ protected CompletableFuture getMessageAsync(CompletableFuture future, String clientHost, String groupId, String topicId, int queueId, int batchSize, MessageFilter filter, PopConsumerRecord.RetryType retryType) {