fix(mq-starter-core): driver.stop() 异常不再跳过停机强刷尾巴

review 发现 #8:stop() 里 driver.stop() 裸调用,抛异常(如 RocketMQ shutdown
出错/Redis 连接失败)会跳过定时器关闭、全部 forceFlush、running=false——
缓冲里已逐条 ACK 的批直接丢,且 SmartLifecycle 回调没走完会卡 Spring 关闭。

修复:driver.stop() 包 try/catch,异常打 error 日志后继续走完停机流程。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 02:55:04 +01:00
parent 02469dce11
commit 0e8e3762d2
2 changed files with 34 additions and 1 deletions

View File

@@ -146,7 +146,13 @@ public class MqListenerRegistry implements SmartLifecycle {
@Override public synchronized void stop() {
// 停机顺序driver 先停(不再有新消息入缓冲)→ 定时器停 → 强刷残留批 → 置 running。
driver.stop();
// driver.stop 异常必须吞掉:否则跳过后面整个强刷尾巴(缓冲里已 ACK 的批直接丢),
// 且 SmartLifecycle 回调没走完会卡 Spring 关闭。
try {
driver.stop();
} catch (Exception e) {
log.error("[mq] driver 停止失败(吞掉,继续停机流程强刷残留批)", e);
}
if (flushScheduler != null) {
// 温和关闭(不打断正在执行的最后一次刷出;后续执行取消,残留批由下方 forceFlush 接住)。
flushScheduler.shutdown();

View File

@@ -223,6 +223,33 @@ class MqListenerRegistryTest {
assertNull(reg.getFlushScheduler(), "停机后定时器应已关闭置空");
}
/** fake driverstop 时抛异常。 */
static class FailingStopDriver implements MqDriver {
MqSubscription captured;
@Override public void send(String topic, MqMessage m) {}
@Override public void stop() { throw new RuntimeException("driver stop fail"); }
@Override public void subscribe(MqSubscription s) { this.captured = s; }
}
@Test
void stop_whenDriverStopThrows_stillFlushesLeftoverAndCompletes() throws Exception {
List<String> events = new ArrayList<>();
LeftoverBiz biz = new LeftoverBiz(events);
MqListenerAnnotationBeanPostProcessor bpp = new MqListenerAnnotationBeanPostProcessor();
bpp.postProcessAfterInitialization(biz, "biz");
FailingStopDriver driver = new FailingStopDriver();
MqListenerRegistry reg = new MqListenerRegistry(driver, bpp.getEndpoints());
reg.start();
driver.captured.getListener().onMessage(MqMessage.of("k1", "", "a"));
driver.captured.getListener().onMessage(MqMessage.of("k2", "", "b")); // batchSize=32 条不满批
reg.stop(); // driver.stop 抛异常不得中断停机流程
assertEquals(Arrays.asList("flush:2"), events, "driver.stop 抛异常也必须强刷残留批(否则已 ACK 的批静默丢)");
assertNull(reg.getFlushScheduler(), "driver.stop 抛异常也应关闭定时器");
assertFalse(reg.isRunning(), "driver.stop 抛异常也应完成停机置 running=false");
}
@Test
void stop_waitsForInFlightTimedFlushBeforeReturning() throws Exception {
BatchBiz biz = new BatchBiz();