QuarkusIntegrationTest无法顺序执行两次:Camel致应用提前关闭
Quarkus集成测试多类运行时Camel自动关闭问题
问题现象
执行mvn verify时,单个集成测试能正常运行;当存在两个及以上集成测试时,第二个测试启动后,应用会被Camel SimpleMainShutdownStrategy立即关闭,后续无操作且构建永久挂起。
日志对比
第一个测试正常运行日志
2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Routes startup (total:8 started:8) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route1 (activemq://queue:myqueue) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route2 (activemq://queue:myqueue.online) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started main (direct://main) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started individual-endpoint (direct://individual-endpoint) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started send-and-retry (direct://send-and-retry) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started mapping-response (direct://mapping-response) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started reply-queue (direct://reply-queue) 2023-01-04 15:43:04,560 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route3 (direct://receive.ack.response) 2023-01-04 15:43:04,561 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.14.1 (camel-1) started in 593ms (build:0ms init:206ms start:387ms) 2023-01-04 15:43:04,562 DEBUG [org.apa.cam.imp.DefaultCamelContext] (main) start() took 594 millis 2023-01-04 15:43:04,567 DEBUG [org.apa.cam.mai.SimpleMainShutdownStrategy] (camel-main) Await shutdown to complete 2023-01-04 15:43:04,583 INFO [com.example.MyLogger] (main) Application is up 2023-01-04 15:43:04,583 INFO [com.example.MyLogger] (main) Application is up 2023-01-04 15:43:04,767 INFO [com.example.ShutdownController] (main) Setting pre shutdown sleep time to 10 seconds. 2023-01-04 15:43:04,767 INFO [io.quarkus] (main) my-app 0.0.1-SNAPSHOT on JVM (powered by Quarkus 2.7.5.Final) started in 3.124s. Listening on: http://0.0.0.0:8081 2023-01-04 15:43:04,768 INFO [io.quarkus] (main) Profile integration activated. ...(application runs normally; sends a message to in memory AMQ broker and Camel starts consuming from the queue)
第二个测试异常关闭日志
2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Routes startup (total:8 started:8) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route1 (activemq://queue:myqueue) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route2 (activemq://queue:myqueue.online) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started main (direct://main) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started individual-endpoint (direct://individual-endpoint) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started send-and-retry (direct://send-and-retry) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started mapping-response (direct://mapping-response) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started reply-queue (direct://reply-queue) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Started route3 (direct://receive.ack.response) 2023-01-04 15:43:59,606 INFO [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.14.1 (camel-1) started in 452ms (build:0ms init:202ms start:250ms) 2023-01-04 15:43:59,607 DEBUG [org.apa.cam.imp.DefaultCamelContext] (main) start() took 453 millis 2023-01-04 15:43:59,631 INFO [com.example.MyLogger] (main) Application is up 2023-01-04 15:43:59,631 INFO [com.example.MyLogger] (main) Application is up 2023-01-04 15:43:59,617 DEBUG [org.apa.cam.mai.SimpleMainShutdownStrategy] (camel-main) Await shutdown to complete 2023-01-04 15:43:59,756 INFO [com.example.MyLogger] (main) Application is down 2023-01-04 15:43:59,756 INFO [com.example.MyLogger] (main) Application is down 2023-01-04 15:43:59,757 DEBUG [org.apa.cam.mai.SimpleMainShutdownStrategy] (main) Shutdown called 2023-01-04 15:43:59,757 DEBUG [org.apa.cam.mai.MainLifecycleStrategy] (main) CamelContext: camel-1 is stopping, triggering shutdown of the JVM.
相关代码
ShutdownController类(Quarkus关闭监听器)
public class ShutdownController implements ShutdownListener { static final Logger LOGGER = LoggerFactory.getLogger(ShutdownController.class); private final long preShutdownSleep; public ShutdownController() { this.preShutdownSleep = (Long)ConfigProvider.getConfig().getOptionalValue("com.example.shutdown-controller.pre-shutdown-sleep", Long.class).orElse(ProfileManager.getLaunchMode() == LaunchMode.TEST ? 0L : 10L); LOGGER.info("Setting pre shutdown sleep time to {} seconds.", this.preShutdownSleep); } public ShutdownController(final int preShutdownSleep) { this.preShutdownSleep = (long)preShutdownSleep; } public void preShutdown(final ShutdownNotification notification) { LOGGER.info("Pre shutdown received. Waiting fully functionally for {} seconds.", this.preShutdownSleep); this.sleep(); LOGGER.info("Continuing shutdown"); notification.done(); } private void sleep() { try { TimeUnit.SECONDS.sleep(this.preShutdownSleep); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } } }
SimpleMainShutdownStrategy核心逻辑
@Override public boolean isRunAllowed() { return !completed.get(); } @Override public void addShutdownListener(ShutdownEventListener listener) { listeners.add(listener); } @Override public boolean shutdown() { if (completed.compareAndSet(false, true)) { LOG.debug("Shutdown called"); latch.countDown(); for (ShutdownEventListener l : listeners) { try { LOG.trace("ShutdownEventListener: {}", l); l.onShutdown(); } catch (Throwable e) { // ignore as we must continue LOG.debug("Error during ShutdownEventListener: {}. This exception is ignored.", l, e); } } return true; } return false; } @Override public void await() throws InterruptedException { LOG.debug("Await shutdown to complete"); latch.await(); }
集成测试类结构
@QuarkusIntegrationTest @QuarkusTestResource(value = WiremockResource.class, restrictToAnnotatedClass = true) @QuarkusTestResource(value = InMemoryAMQResource.class, restrictToAnnotatedClass = true) @TestProfile(MyIntegrationTestProfile.class) class MyOutboundMessageFormatIT extends MyIntegrationTestSupport { // 基类包含connection、session等字段,用于@BeforeAll和@BeforeEach等方法 private static final Logger LOG = Logger.getLogger(MyOutboundMessageFormatIT.class); @InjectInMemoryAMQ protected BrokerService brokerService; @InjectWiremock protected WireMockServer wireMockServer; @Override WireMockServer getWiremock() { return wireMockServer; } @Override BrokerService getBroker() { return brokerService; } @BeforeAll protected static void start() throws Exception { LOG.debug("Connecting to AMQ"); connectToAMQ(); } @AfterAll protected static void stop() throws Exception { LOG.debug("Stopping AMQ"); session.close(); connection.stop(); connection.close(); } @BeforeEach protected void stub() { // 全局stub配置 stubAllFieldsOK(); stubOauthOK(); } @AfterEach protected void clearWiremockRequestsJournal() throws JMSException { // 清空回复队列以便下一个测试使用 consumeOnlineReply(); assertEmptyNonOnlineReply(); waitTillTokenExpires(); wireMockServer.resetRequests(); } ... }
问题原因分析
- Camel ShutdownStrategy的静态状态未重置:
SimpleMainShutdownStrategy中的completed是类级别的AtomicBoolean变量,第一个测试结束时调用shutdown()将其设置为true。由于Quarkus集成测试默认在同一个JVM进程中运行多个测试类,第二个测试启动时,Camel初始化复用了该类的静态状态,导致isRunAllowed()返回false,触发立即关闭流程。 - 测试模式下ShutdownController无延迟:
ShutdownController在测试模式下preShutdownSleep被设置为0秒,第一个测试结束时关闭流程无延迟完成,未给Camel状态重置留缓冲时间;第二个测试启动后,Camel检测到completed已为true,直接执行关闭,导致应用在测试运行前就被终止。 - Quarkus与Camel生命周期协同问题:Quarkus集成测试的应用重启逻辑未覆盖Camel的全局静态状态,导致Camel的ShutdownStrategy状态在测试间残留,引发异常关闭。
内容的提问来源于stack exchange,提问作者WesternGun
相关产品推荐
相关产品推荐

