-
Notifications
You must be signed in to change notification settings - Fork 329
Description
问题摘要 / 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([email protected]/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
- MockMvc 服务器启动应该有超时机制(建议 30-60 秒)
- 超时后应抛出清晰的异常信息,说明服务器启动失败的原因
- 测试应该快速失败(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`,循环将永远执行
- ❌ 没有错误信息输出,难以诊断问题
可能导致启动失败的原因:
- 端口被占用(默认端口可能与其他服务冲突)
- 系统资源不足
- 防火墙阻止本地连接
- MockController 未正确注册或启动
重现步骤 / Steps to Reproduce
稳定重现(推测)
- 占用 MockMvc 使用的默认端口
- 运行任何使用 `@MvcTest` 注解的测试
- 测试将卡在 `beforeTestClass` 阶段
偶发重现(本次遇到)
- 执行 `mvn clean install`
- 等待构建进行到 `fit-validation-hibernate-jakarta` 模块
- 测试在 `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
- 我提供的信息准确完整
- 我愿意协助测试问题修复