test(e2e): 子进程统一登记 + 退出兜底 SIGKILL —— 修连跑偶发失败
套件有 8 处 spawn,各自 finally 只发 kill()(SIGTERM)就 process.exit(), 被控端可能来不及退出 → 下一轮固定端口仍被占用,表现为「连跑第二次偶发失败、 重跑又绿」这种最难查的形态。 做法:把 spawn 包一层统一登记(同名函数遮蔽,调用点零改动),进程退出时对 仍存活的子进程 SIGKILL。单点收口,将来新增 spawn 也不会漏。 实测:连跑两次 34/34,退出后 pgrep 无残留 agent 进程。
This commit is contained in:
1 parent
eebfb45f73
commit
5e32d7fa55
1 file changed
+29
-1
+29
-1
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* 运行:node tests/e2e/run.mjs
|
||||
*/
|
||||
import { spawn } from 'node:child_process';
|
||||
import { spawn as rawSpawn } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createRequire } from 'node:module';
|
||||
@@ -25,6 +25,34 @@ const RawWebSocket = requireFromClient('ws');
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(__dirname, '../..');
|
||||
|
||||
/**
|
||||
* 子进程登记表 + 兜底清理。
|
||||
*
|
||||
* 为什么需要:本套件有 8 处 spawn(主 agent / TLS / ed25519 / hub / fs / monitor…),
|
||||
* 各自的 finally 只做 `child.kill()`(SIGTERM),随后立刻 process.exit —— 被控端
|
||||
* 可能还没来得及退出就被父进程带走,**下一轮连跑时固定端口仍被占用**,表现为
|
||||
* 「连跑第二次偶发失败」(历史真机现象,重跑又绿,最难查)。
|
||||
*
|
||||
* 做法:包一层 spawn,把所有子进程登记下来;进程退出时统一 SIGKILL。
|
||||
* 调用点无需改动(同名函数遮蔽),单点收口也不会漏掉将来新增的 spawn。
|
||||
*/
|
||||
const liveChildren = new Set();
|
||||
function spawn(...args) {
|
||||
const child = rawSpawn(...args);
|
||||
liveChildren.add(child);
|
||||
child.once('exit', () => liveChildren.delete(child));
|
||||
return child;
|
||||
}
|
||||
process.on('exit', () => {
|
||||
for (const child of liveChildren) {
|
||||
try {
|
||||
child.kill('SIGKILL');
|
||||
} catch {
|
||||
/* 已退出或不可杀:忽略 */
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = 18765;
|
||||
const TLS_PORT = 18772;
|
||||
const ED25519_PORT = 18781;
|
||||
|
||||
Reference in new issue
Block a user