使用concurrently并行执行脚本时始终返回退出码1的问题
从你提供的日志和代码来看,核心问题是测试进程正常退出后,Mock服务进程收到SIGTERM信号却没有正确返回退出码0,导致整个Concurrently命令最终返回退出码1。下面一步步分析原因并给出解决方案:
问题核心分析
从日志可以明确看到:
[1] npm run test-single-run exited with code 0 --> Sending SIGTERM to other processes..
[0] node ./tools/mock-webapi/mock-webapi.js exited with code 1
测试进程成功结束后,Concurrently向Mock服务发送了SIGTERM信号,但Mock服务最终还是返回了错误退出码。你的信号处理代码存在一个关键误区,再加上可能的异步清理不彻底,导致了这个问题:
- SIGKILL信号无法被捕获:你监听了SIGKILL信号,但这个信号是操作系统强制杀死进程的信号,Node.js进程根本无法捕获和处理,这个监听完全无效,反而可能干扰正常的退出逻辑。
- 信号处理逻辑不够健壮:没有处理重复触发信号的情况,也没有设置超时兜底,可能因为server.close()卡住导致进程无法正常退出。
解决方案步骤
1. 修正信号处理代码
首先移除无效的SIGKILL监听,然后优化SIGTERM和SIGINT的处理逻辑,确保进程能优雅退出:
let isShuttingDown = false; // 只保留可捕获的信号监听 process.on('SIGTERM', handleGracefulShutdown); process.on('SIGINT', handleGracefulShutdown); function handleGracefulShutdown(signal) { if (isShuttingDown) return; // 防止重复触发退出逻辑 isShuttingDown = true; console.log(`Received ${signal}, starting graceful shutdown for mock server (PID: ${process.pid})`); // 关闭Web服务 server.close((err) => { if (err) { console.error('Failed to close mock server:', err); process.exit(1); } console.log('Mock server closed successfully'); process.exit(0); }); // 设置超时兜底,防止server.close()卡住 setTimeout(() => { console.error('Mock server shutdown timed out, forcing exit'); process.exit(0); }, 5000); // 5秒超时,可根据实际情况调整 }
2. 验证Mock服务单独退出行为
先单独运行Mock服务,手动发送SIGTERM信号验证是否能正常返回退出码0:
- 启动Mock服务:
node ./tools/mock-webapi/mock-webapi.js - 获取进程PID:
ps aux | grep mock-webapi.js(Linux/macOS)或用任务管理器(Windows) - 发送SIGTERM信号:
kill -SIGTERM <PID>(Linux/macOS)或taskkill /PID <PID> /T(Windows) - 查看退出码:
echo $?(Linux/macOS)或$LASTEXITCODE(PowerShell)
如果单独测试时退出码是0,说明信号处理逻辑没问题,再回到Concurrently场景测试。
3. 优化Concurrently命令与版本
- 升级Concurrently到最新版本,旧版本可能存在参数兼容问题:
npm install concurrently@latest --save-dev - 可以加上
--prefix "[{name}]"参数让日志更清晰,方便排查:concurrently -k --success first --prefix "[{name}]" "node ./tools/mock-webapi/mock-webapi.js" "npm run test-single-run"
4. 清理额外资源(如果需要)
如果Mock服务使用了其他资源(比如数据库连接、定时器、WebSocket连接等),需要在handleGracefulShutdown函数中手动清理,确保事件循环能顺利清空:
function handleGracefulShutdown(signal) { // ... 原有逻辑 ... // 清理定时器 clearInterval(yourIntervalId); clearTimeout(yourTimeoutId); // 关闭数据库连接(如果有) // dbClient.end(); }
总结
通过移除无效的SIGKILL监听、优化优雅退出逻辑、验证单独运行行为,再配合最新版的Concurrently,应该就能解决退出码异常的问题。
内容的提问来源于stack exchange,提问作者Adrian Moisa

