apache / apache/rocketmq

[Bug] Shared mutable batchAckIndexList in PopBufferMergeService causes toStoreBits update failure when enablePopBatchAck and appendAckAsync are both enabled

Open
#10,256 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
22.6k
Forks
12k
Avg merge
2d 20h
Merged PRs (30d)
26

Description

### Before Creating the Bug Report

- [x] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions).

- [x] I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate.

- [x] I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.

### Runtime platform environment

All platforms

### RocketMQ version

develop branch

### JDK Version

JDK 8+

### Describe the Bug

In `PopBufferMergeService.scan()`, when `enablePopBatchAck` is `true`, the method collects ACK indices into a class-level shared variable `batchAckIndexList`, then passes this same list reference to `putBatchAckToStore()`. Immediately afterwards, it calls `indexList.clear()` in the `finally `block.

When `appendAckAsync `is also `true`, `putBatchAckToStore()` performs an asynchronous write. The async callback `handleBatchAckPutMessageResult `captures the same list reference. By the time the callback executes, the list has already been cleared (or contains stale data from the next loop iteration).

As a result, `pointWrapper.getToStoreBits()` is never updated correctly. This causes `isCkDoneForFinish()` to always return `false`, preventing `PopCheckPointWrapper `objects from ever being removed from the `commitOffsets `queue (an unbounded `LinkedBlockingDeque`). Over time, this leads to unbounded memory growth and eventually OOM.

### Steps to Reproduce

Set `enablePopBatchAck=true` and `appendAckAsync=true`
Run a Pop consumer with moderate traffic
Observe that `commitOffsets `queue size grows indefinitely

### What Did You Expect to See?

`toStoreBits `should be correctly updated after async IO completes. `commitOffsets `queue should be drained normally.

### What Did You See Instead?

`toStoreBits `remains 0. `commitOffsets `queue grows without bound.

### Additional Context

Fix: Pass a defensive copy `new ArrayList<>(indexList)` instead of the shared reference.

Contributor guide

Open the contributing guide

Research direction

Start in PopBufferMergeService.scan() and trace the batchAckIndexList reference into putBatchAckToStore() and handleBatchAckPutMessageResult(). Reproduce with enablePopBatchAck=true and appendAckAsync=true, then verify that async completion updates toStoreBits and allows the commitOffsets queue to drain instead of growing indefinitely.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.