From fe2896a3582336ad81c81c902ad545b07b0065bf Mon Sep 17 00:00:00 2001 From: Ajax-1 <85230126+Ajax-1@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:01:59 -0400 Subject: [PATCH] [ISSUE #11498] Skip timer log scan in checkAndReviseMetrics when no eligible timer slot exists TimerWheel.checkPhyPos returns Long.MAX_VALUE when no slot matches, which was passed directly to TimerLog.getWholeBuffer and produced a misleading "Offset not matched" warning. Skip the scan for the sentinel while keeping the metrics revision. --- .../store/timer/TimerMessageStore.java | 3 +- .../store/timer/TimerMessageStoreTest.java | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java index 9608af3213d..07c57c2caee 100644 --- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java @@ -1339,7 +1339,8 @@ public void checkAndReviseMetrics() { LinkedList sbrs = new LinkedList<>(); boolean hasError = false; try { - while (true) { + // Long.MAX_VALUE means no eligible slot in the timer wheel, skip the scan but still revise the metrics + while (currOffsetPy != Long.MAX_VALUE) { SelectMappedBufferResult timeSbr = timerLog.getWholeBuffer(currOffsetPy); if (timeSbr == null) { break; diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java index e62075cf67b..8264b35a7f9 100644 --- a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java @@ -608,6 +608,51 @@ public void testRollMessage() throws Exception { storeConfig.setTimerRollWindowSlot(Integer.MAX_VALUE); } + @Test + public void testCheckAndReviseMetricsWithEmptyTimerWheel() throws Exception { + String topic = "TimerTest_testCheckAndReviseMetricsWithEmptyTimerWheel"; + TimerMessageStore timerMessageStore = createTimerMessageStore(null, true); + timerMessageStore.load(); + + // the timer log has a mapped file, but no slot in the timer wheel is eligible + TimerLog timerLog = Mockito.spy(timerMessageStore.getTimerLog()); + Field timerLogField = TimerMessageStore.class.getDeclaredField("timerLog"); + timerLogField.setAccessible(true); + timerLogField.set(timerMessageStore, timerLog); + assertNotNull(timerLog.getMappedFileQueue().getLastMappedFile(0)); + timerMessageStore.currReadTimeMs = System.currentTimeMillis() / precisionMs * precisionMs; + assertEquals(Long.MAX_VALUE, timerMessageStore.getTimerWheel().checkPhyPos(timerMessageStore.currReadTimeMs, 0)); + + timerMessageStore.getTimerMetrics().getTopicPair(topic).getCount().set(5); + timerMessageStore.checkAndReviseMetrics(); + + verify(timerLog, Mockito.never()).getWholeBuffer(Long.MAX_VALUE); + assertEquals(0, timerMessageStore.getTimerMetrics().getTimingCount(topic)); + } + + @Test + public void testCheckAndReviseMetricsWithValidSlot() throws Exception { + String topic = "TimerTest_testCheckAndReviseMetricsWithValidSlot"; + TimerMessageStore timerMessageStore = createTimerMessageStore(null, true); + timerMessageStore.load(); + + long curr = System.currentTimeMillis() / precisionMs * precisionMs; + timerMessageStore.currReadTimeMs = curr; + timerMessageStore.currWriteTimeMs = curr; + long delayedTime = curr + 2 * precisionMs; + for (int i = 0; i < 2; i++) { + MessageExtBrokerInner inner = buildMessage(delayedTime, topic, false); + MessageAccessor.putProperty(inner, MessageConst.PROPERTY_REAL_TOPIC, topic); + assertTrue(timerMessageStore.doEnqueue(i * 100L, 100, delayedTime, inner, false)); + } + assertEquals(0, timerMessageStore.getTimerWheel().checkPhyPos(curr, 0)); + + timerMessageStore.getTimerMetrics().getTopicPair(topic).getCount().set(5); + timerMessageStore.checkAndReviseMetrics(); + + assertEquals(2, timerMessageStore.getTimerMetrics().getTimingCount(topic)); + } + public ByteBuffer getOneMessage(String topic, int queue, long offset, int timeout) throws Exception { int retry = timeout / 100; while (retry-- > 0) {