messere1 opened a new issue, #11379:
URL: https://github.com/apache/rocketmq/issues/11379

   ### Background
   
   `MessageStoreDispatcherImpl#doScheduleDispatch` is the tiered-storage 
dispatch loop. Before entering the group-commit append loop it preflights the 
*first* offset and guards both lookups (added by #8965 / issue #8955, which 
fixed the dispatch thread exiting on NPE):
   
   ```java
   CqUnit cqUnit = consumeQueue.get(currentOffset);
   if (cqUnit == null) { log.warn(...); return ...completedFuture(false); }
   SelectMappedBufferResult message = 
defaultStore.selectOneMessageByOffset(cqUnit.getPos(), cqUnit.getSize());
   if (message == null) { log.warn(...); return ...completedFuture(false); }
   ```
   
   However, the append loop that follows re-fetches the CQ unit and the message 
for every subsequent offset **without any of those guards**:
   
   ```java
   for (; offset < targetOffset; offset++) {
       cqUnit = consumeQueue.get(offset);
       bufferSize += cqUnit.getSize();                       // NPE if cqUnit 
== null
       ...
       message = defaultStore.selectOneMessageByOffset(cqUnit.getPos(), 
cqUnit.getSize());
       appendingBufferList.add(message);
       AppendResult result = flatFile.appendCommitLog(message); // NPE/garbage 
if message == null
       ...
   }
   ```
   
   ### Problem
   
   1. **Mid-loop NPE.** `ConsumeQueueInterface#get` returns null once the 
consume queue has been truncated past the requested offset, and 
`selectOneMessageByOffset` returns null once the commitlog data has been 
deleted (expired files). Both events happen through the ordinary expiry sweeps 
while the dispatcher is catching up (e.g. after a downtime longer than the file 
retention). The preflight only proves the *first* offset of the batch is still 
there; the batch spans up to `tieredStoreGroupCommitCount` more offsets, so the 
lookups can fail inside the loop. The resulting NPE escapes 
`doScheduleDispatch` (only `ConsumeQueueException` is caught there).
   
   2. **Message buffer leak.** Every `SelectMappedBufferResult` fetched inside 
the loop is added to `appendingBufferList` and is only ever released through 
`GroupCommitContext#release()`. When the loop dies with the NPE above, the 
context is never constructed, so every buffer fetched before the failing offset 
leaks its `MappedFile` reference.
   
      The same leak exists on a non-exceptional path: if `appendCommitLog` 
returns `SUCCESS` but the following `appendConsumeQueue` fails on the first 
message, the loop breaks with `appendingBufferList` non-empty and 
`dispatchRequestList` empty. The context is built, but the `if 
(!dispatchRequestList.isEmpty())` block — the only place that hands it to 
`commitAsync`/`constructIndexFile`, both of which release it — never runs. The 
context is silently dropped and its buffers leak.
   
   ### Impact
   
   - Repeated mapped-buffer/reference leaks on queues whose data expires while 
dispatch lags, and on flat files that fail a consume-queue append after a 
successful commitlog append — eventually holding expired commitlog files from 
being cleaned.
   - The NPE aborts the current dispatch round for the queue (the dispatcher 
thread itself survives because `dispatch()` catches `Throwable`), so dispatch 
progress depends on the next cycle re-failing the preflight instead of 
degrading gracefully.
   
   ### Suggestion
   
   Mirror the preflight guards inside the loop (`break` on a missing CQ unit or 
message so the partial batch still commits and the context is still released), 
and release the context when nothing was appended to the consume queue so no 
code path will consume it.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to