ProcessLauncher.js
3.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* ProcessLauncher — spawn 本地子进程
*
* start() 返回 { process, stdin, stdout, stderr }
* 供 PipeTransport 绑定 stdin/stdout 通信
*/
const { spawn } = require('child_process');
const { Launcher } = require('./Launcher');
class ProcessLauncher extends Launcher {
constructor() {
super();
this.child = null;
}
/**
* @param {object} options
* @param {string} options.command - 可执行命令
* @param {string[]} options.args - 参数列表
* @param {object} [options.env] - 环境变量
* @param {string} [options.cwd] - 工作目录
* @returns {Promise<{ process, stdin, stdout, stderr }>}
*/
async start(options = {}) {
const { command, args = [], env, cwd } = options;
const processEnv = { ...process.env, ...(env || {}) };
delete processEnv.NODE_OPTIONS;
delete processEnv.ELECTRON_RUN_AS_NODE;
delete processEnv.CLAUDECODE;
console.log(`[ProcessLauncher] 启动: ${command} ${args.join(' ')}`);
this.child = spawn(command, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env: processEnv,
cwd,
});
// 等待进程启动(简单延时,进程 error 会 reject)
await new Promise((resolve, reject) => {
this.child.on('error', reject);
setTimeout(resolve, 300);
});
return {
process: this.child,
stdin: this.child.stdin,
stdout: this.child.stdout,
stderr: this.child.stderr,
};
}
/**
* 终止子进程并等待其完全退出
*
* 关闭策略(确保 Node.js event loop 不被悬挂的子进程阻塞):
* 1. 关闭 stdin(发送 EOF,给子进程优雅退出的机会)
* 2. 发送 SIGTERM
* 3. 超时后强制 SIGKILL
* 4. 等待 exit 事件确保进程完全退出
* 5. 销毁 stdio 流,释放 event loop 引用
*
* @param {number} [timeoutMs=5000] - SIGTERM 超时时间,超时后发送 SIGKILL
*/
async stop(timeoutMs = 5000) {
if (!this.child) return;
const child = this.child;
this.child = null;
// 已经退出,直接清理流
if (child.exitCode !== null || child.signalCode !== null) {
this._destroyStreams(child);
return;
}
return new Promise((resolve) => {
// 超时后 SIGKILL 强杀
const forceKillTimer = setTimeout(() => {
console.log('[ProcessLauncher] SIGTERM 超时,发送 SIGKILL');
try { child.kill('SIGKILL'); } catch { }
}, timeoutMs);
// 等待进程退出
child.once('exit', (code, signal) => {
clearTimeout(forceKillTimer);
this._destroyStreams(child);
console.log(`[ProcessLauncher] 子进程已退出 (code=${code}, signal=${signal})`);
resolve();
});
// 关闭 stdin,通知子进程 EOF
try { child.stdin.end(); } catch { }
// 发送 SIGTERM
try { child.kill('SIGTERM'); } catch { }
});
}
/**
* 销毁子进程的 stdio 流,释放 event loop 引用
* @private
*/
_destroyStreams(child) {
for (const stream of [child.stdin, child.stdout, child.stderr]) {
if (stream && !stream.destroyed) {
try {
stream.removeAllListeners();
stream.destroy();
} catch { }
}
}
}
}
module.exports = { ProcessLauncher };