[Bug] Shared mutable batchAckIndexList in PopBufferMergeService causes toStoreBits update failure when enablePopBatchAck and appendAckAsync are both enabled
- 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
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