fix(mq-starter-core): 批量超时缺省时钟改单调源,免受 NTP 回拨影响

review 发现 #3:缺省 clock = System::currentTimeMillis,超时判断是差值
比较(now - batchStartAtMs),墙钟被 NTP 回拨会让差值变负,该批多躺回拨量,
突破"刷出延迟上界一个超时窗口"的文档承诺。

修复:缺省 clock 换 System.nanoTime()/1_000_000(单调,只用于相对差值)。
clock 注入口子不变,既有假时钟测试不受影响。

测试说明:回拨行为单测无法注入(系统时钟不可控),新增缺省时钟冒烟测试
守护纳秒→毫秒换算(错用裸 nanoTime 会让任何批立即被判超时),非严格
TDD RED——超时语义本身已有注入假时钟的用例覆盖。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 03:35:18 +01:00
parent 7e718dd632
commit b4ec55baf5
2 changed files with 19 additions and 1 deletions

View File

@@ -187,7 +187,9 @@ public class MqConsumeDispatcher {
public static class Builder {
private int batchSize = 1;
private int batchTimeoutMs; // 缺省 0 = 关闭(注解缺省 60s 由 registry 传入)
private LongSupplier clock = System::currentTimeMillis;
// 单调时钟:只做相对差值(now - batchStartAtMs),用 nanoTime 免受 NTP 回拨影响
// (墙钟回拨会让差值变负,批多躺回拨量,突破"延迟上界一个超时窗口"承诺)。
private LongSupplier clock = () -> System.nanoTime() / 1_000_000;
private boolean idempotent = false;
private MqIdempotentStore store;
private MqConsumeErrorHandler errorHandler;

View File

@@ -334,6 +334,22 @@ class MqConsumeDispatcherTest {
assertFalse(d.needsTimeoutFlush(), "关闭超时的批量监听器不应纳入定时扫描");
}
@Test
void defaultClock_freshBatchNotDue() throws Exception {
// 不注入 clock,守护缺省单调时钟的纳秒→毫秒换算:错用裸 nanoTime 会让差值以纳秒计,
// 刚入缓冲的批立即被判超时刷出。
List<List<MqMessage>> consumed = new ArrayList<>();
MqConsumeDispatcher d = MqConsumeDispatcher.builder()
.batchSize(3).batchTimeoutMs(60_000)
.consumer(consumed::add)
.build();
d.dispatch(MqMessage.of("k1", "", "a"));
d.flushIfTimeout();
assertEquals(0, consumed.size(), "刚入缓冲远未到 60s,缺省时钟不应判超时");
}
@Test
void forceFlush_flushesLeftovers() throws Exception {
List<List<MqMessage>> consumed = new ArrayList<>();