Skip to content

🐛 [Test] MockMvcListener 无限等待导致测试卡死 30+ 分钟 #395

Closed

Description

@CodeCasterX

问题摘要 / Issue Summary

MockMvcListener.beforeTestClass() 中的服务器启动等待逻辑缺少超时机制,当 MockMvc 服务器启动失败时会导致测试无限等待,卡死 30+ 分钟。

版本信息 / Version Information

v3.6.2-SNAPSHOT (分支: fit-enhancement-fastjson-upgrade, Commit: e08cb48)

操作系统 / Operating System

macOS (Apple Silicon)

发生了什么?/ What happened?

在执行 `mvn clean install` 进行完整构建时,测试在 `fit-validation-hibernate-jakarta` 模块的 `ValidationDataControllerTest` 处卡死,已持续运行 30+ 分钟仍未结束。

通过 `jstack` 分析发现主线程卡在无限循环中:

```
"main" #1 prio=5 os_prio=31 cpu=23035.57ms elapsed=1814.34s tid=0x00007fed3e009a00 nid=0x1503 waiting on condition
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@17.0.17/Native Method)
at modelengine.fitframework.util.ThreadUtils.sleep(ThreadUtils.java:55)
at modelengine.fitframework.test.domain.listener.MockMvcListener.beforeTestClass(MockMvcListener.java:76)
```

已等待 1814.34 秒(约 30 分钟),正常的 Maven 构建应在 8-15 分钟内完成。

期望的行为 / Expected Behavior

  1. MockMvc 服务器启动应该有超时机制(建议 30-60 秒)
  2. 超时后应抛出清晰的异常信息,说明服务器启动失败的原因
  3. 测试应该快速失败(fail-fast),而不是无限等待

根本原因分析 / Root Cause Analysis

问题代码位置:
`framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/listener/MockMvcListener.java:75-78`

```java
boolean started = this.isStarted(mockMvc);
while (!started) {
ThreadUtils.sleep(100);
started = this.isStarted(mockMvc);
}
```

问题:

  • ❌ 没有超时限制的无限循环
  • ❌ 如果 `isStarted()` 持续返回 `false`,循环将永远执行
  • ❌ 没有错误信息输出,难以诊断问题

可能导致启动失败的原因:

  1. 端口被占用(默认端口可能与其他服务冲突)
  2. 系统资源不足
  3. 防火墙阻止本地连接
  4. MockController 未正确注册或启动

重现步骤 / Steps to Reproduce

稳定重现(推测)

  1. 占用 MockMvc 使用的默认端口
  2. 运行任何使用 `@MvcTest` 注解的测试
  3. 测试将卡在 `beforeTestClass` 阶段

偶发重现(本次遇到)

  1. 执行 `mvn clean install`
  2. 等待构建进行到 `fit-validation-hibernate-jakarta` 模块
  3. 测试在 `ValidationDataControllerTest` 处卡死

重现频率:偶发(本次在第 145/160 个模块时发生)

相关日志 / Relevant Logs

Maven 构建日志

```
[INFO] Building FIT Hibernate Validation Jakarta 3.6.2-SNAPSHOT [145/160]
[INFO] --- surefire:3.5.3:test (default-test) @ fit-validation-hibernate-jakarta ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running modelengine.fitframework.validation.ValidationDataControllerTest
12月 31, 2025 10:10:40 上午 org.hibernate.validator.internal.util.Version
INFO: HV000001: Hibernate Validator 9.0.1.Final

之后无任何输出,卡死在此处

```

进程信息

```bash

Surefire 进程已运行 30+ 分钟

$ ps -p 10934 -o etime=
34:40

线程堆栈显示在无限循环中

$ jstack 10934
"main" #1 ... elapsed=1814.34s ... TIMED_WAITING (sleeping)
at modelengine.fitframework.test.domain.listener.MockMvcListener.beforeTestClass(MockMvcListener.java:76)
```

构建统计

  • 构建开始时间:10:10:40
  • 当前时间:10:40+
  • 卡住时长:30+ 分钟
  • 正常构建时长:8-15 分钟

建议的修复方案 / Suggested Fix

方案 1:添加超时机制(推荐)

```java
@OverRide
public void beforeTestClass(TestContext context) {
Class<?> testClass = context.testClass();
if (AnnotationUtils.getAnnotation(testClass, EnableMockMvc.class).isEmpty()) {
return;
}
MockMvc mockMvc = new MockMvc(this.port);
context.plugin().container().registry().register(mockMvc);

// 添加超时机制
long startTime = System.currentTimeMillis();
long timeout = 30_000; // 30 秒超时
boolean started = this.isStarted(mockMvc);

while (!started) {
    if (System.currentTimeMillis() - startTime > timeout) {
        throw new IllegalStateException(StringUtils.format(
            "MockMvc server failed to start within {0}ms. [port={1}]",
            timeout, this.port));
    }
    ThreadUtils.sleep(100);
    started = this.isStarted(mockMvc);
}

}
```

方案 2:增强错误诊断

```java
private boolean isStarted(MockMvc mockMvc) {
MockRequestBuilder builder = MockMvcRequestBuilders.get(MockController.PATH).responseType(String.class);
try (HttpClassicClientResponse response = mockMvc.perform(builder)) {
String content = response.textEntity()
.map(TextEntity::content)
.orElseThrow(() -> new IllegalStateException(StringUtils.format(
"Failed to start mock http server. [port={0}]",
this.port)));
return Objects.equals(content, MockController.OK);
} catch (IOException | ClientException e) {
// 记录详细的失败原因
logger.debug("MockMvc health check failed: {}", e.getMessage());
return false;
}
}
```

方案 3:配置化端口(避免冲突)

允许通过配置或环境变量指定 MockMvc 端口,避免端口冲突导致启动失败。

影响范围 / Impact

  • 严重性:中高(Medium-High)
  • 影响模块:所有使用 `@MvcTest` 注解的测试类
  • 影响场景:
    • ✅ CI/CD 流水线可能卡死
    • ✅ 本地开发时构建时间异常
    • ✅ 难以诊断和调试

额外信息 / Additional Context

当前环境信息

  • Java 版本:OpenJDK 17.0.17
  • Maven 版本:3.9.12
  • 测试框架:JUnit 5 (JUnit Platform)
  • Surefire 版本:3.5.3

相关测试

  • 测试类:`ValidationDataControllerTest`
  • 测试注解:`@MvcTest(classes = {ValidationDataController.class})`
  • 模块:`fit-validation-hibernate-jakarta` (145/160)

临时解决方案

手动终止卡住的测试进程:
```bash

查找 Surefire 进程

jps -l | grep surefire

终止进程

kill -9
```


确认事项 / Confirmations

  • 我已经搜索了现有的 issues,确认这不是重复问题
  • 我已经阅读了项目文档和 FAQ
  • 我提供的信息准确完整
  • 我愿意协助测试问题修复

Activity

  1. self-assigned this
    on Dec 31, 2025
  2. added theissue type on Dec 31, 2025
  3. added a commit that references this issue on Jan 2, 2026
    7fccaf1
  4. added a commit that references this issue on Jan 2, 2026
    065792c
  5. added this to the 3.6.2 milestone on Jan 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in: fitIssues in FIT modulestype: bugA general bug

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions