You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    }
    ...
}

问题原因分析

  1. Camel ShutdownStrategy的静态状态未重置:SimpleMainShutdownStrategy中的completed是类级别的AtomicBoolean变量,第一个测试结束时调用shutdown()将其设置为true。由于Quarkus集成测试默认在同一个JVM进程中运行多个测试类,第二个测试启动时,Camel初始化复用了该类的静态状态,导致isRunAllowed()返回false,触发立即关闭流程。
  2. 测试模式下ShutdownController无延迟:ShutdownController在测试模式下preShutdownSleep被设置为0秒,第一个测试结束时关闭流程无延迟完成,未给Camel状态重置留缓冲时间;第二个测试启动后,Camel检测到completed已为true,直接执行关闭,导致应用在测试运行前就被终止。
  3. Quarkus与Camel生命周期协同问题:Quarkus集成测试的应用重启逻辑未覆盖Camel的全局静态状态,导致Camel的ShutdownStrategy状态在测试间残留,引发异常关闭。

内容的提问来源于stack exchange,提问作者WesternGun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:10:28