Maven Surefire插件多进程执行阻塞问题求助
使用Maven Surefire插件(LTS版本)在JUnit、jBehave两个测试框架执行测试,尝试通过创建线程+生成进程的方式实现并行化,进程执行的Surefire Jar路径通过ManagementFactory.getRuntimeMXBean().getSystemProperties().get("sun.java.command")获取。
代码片段如下:
CustomRunner.java
void run() { ProcessBuilder processBuilder = new ProcessBuilder(commandArray); Map<String, String> environment = processBuilder.environment(); environment.put("platformIndex", String.valueOf(platformIndex)); try { processBuilder.inheritIO(); Process p = processBuilder.start(); LOGGER.info("Is Alive {} {}", p.isAlive(), LocalTime.now()); int statusCode = p.waitFor(); } catch (Exception e) { e.printStackTrace(); } }
EntryPoint.java
for (int i = 0; i < 3; i++) { Thread thread = new Thread(new CustomRunner(commandArray, String.valueOf(i))); thread.start(); threadList.add(thread); } threadList.forEach(thread -> { try { thread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } }); System.exit(exitcode);
运行2-3个线程对应的进程后,执行卡在p.waitFor();30秒后进程退出,出现错误信息:
Surefire is going to kill self fork JVM. The exit has elapsed 30 seconds after System.exit(0)
导致构建失败(非必现),但进程内测试均已通过。尝试延长ForkedProcessTimeoutInSeconds至数分钟无效,求可能原因及缓解方案。
- IO继承引发阻塞:
processBuilder.inheritIO()让子进程共享父进程的标准输入、输出、错误流,多子进程同时输出时易出现流阻塞,导致子进程无法完成退出流程,父进程waitFor()持续等待。 - 嵌套fork破坏Surefire生命周期:Surefire本身自带fork和并行逻辑,手动创建子进程属于嵌套fork,打乱了Surefire对进程的监控逻辑,触发超时自毁机制。
- Jar路径获取不可靠:
sun.java.command返回的是当前JVM启动命令,可能包含冗余参数或路径格式问题,导致子进程Surefire环境与父进程不一致,引发生命周期混乱。 - 进程同步异常:子进程
System.exit(0)未正确通知父进程,叠加IO阻塞,让Surefire误判进程挂起,触发30秒超时。
改用Surefire原生并行(优先推荐)
Surefire内置完善的并行测试支持,无需手动实现线程+进程,直接在pom.xml配置:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire.version}</version> <configuration> <!-- 可选并行模式:methods/classes/perCoreThreadCount等 --> <parallel>methods</parallel> <threadCount>3</threadCount> <!-- 适配jBehave的并行配置 --> <systemPropertyVariables> <jbehave.threads>3</jbehave.threads> </systemPropertyVariables> </configuration> </plugin>原生并行能完美兼容Surefire的进程生命周期管理,避免嵌套fork问题。
移除
inheritIO(),单独处理子进程流
若必须手动创建进程,不要共享父进程IO,单独启动线程处理子进程的输出/错误流,避免阻塞:void run() { ProcessBuilder processBuilder = new ProcessBuilder(commandArray); Map<String, String> environment = processBuilder.environment(); environment.put("platformIndex", String.valueOf(platformIndex)); try { Process p = processBuilder.start(); // 启动线程读取子进程输出流 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { LOGGER.info("Process {} output: {}", platformIndex, line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 启动线程读取子进程错误流 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()))) { String line; while ((line = reader.readLine()) != null) { LOGGER.error("Process {} error: {}", platformIndex, line); } } catch (IOException e) { e.printStackTrace(); } }).start(); LOGGER.info("Is Alive {} {}", p.isAlive(), LocalTime.now()); int statusCode = p.waitFor(); LOGGER.info("Process {} exited with code: {}", platformIndex, statusCode); } catch (Exception e) { e.printStackTrace(); } }修正Surefire Jar路径获取逻辑
放弃依赖sun.java.command,直接使用Maven命令启动子进程,确保环境一致性:List<String> commandArray = Arrays.asList( "mvn", "test", "-DplatformIndex=" + platformIndex, "-Dtest=TargetTestClass" );调整Surefire退出超时参数
若坚持手动fork,需配置forkedProcessExitTimeout(而非ForkedProcessTimeoutInSeconds),控制Surefire等待fork JVM退出的时间:<configuration> <forkedProcessExitTimeout>120</forkedProcessExitTimeout> <!-- 单位:秒 --> </configuration>
内容的提问来源于stack exchange,提问作者avocado

