|
1 | 1 | import { spawn, SpawnOptions } from 'node:child_process'; |
2 | 2 | import * as child_process from 'node:child_process'; |
3 | 3 | import { getGlobalVariable, getGlobalVariablesEnv } from './env'; |
4 | | -import treeKill from 'tree-kill'; |
5 | 4 | import { delimiter, join, resolve } from 'node:path'; |
6 | 5 | import { stripVTControlCharacters, styleText } from 'node:util'; |
| 6 | +import { assertIsError } from './utils'; |
7 | 7 |
|
8 | 8 | interface ExecOptions { |
9 | 9 | silent?: boolean; |
@@ -255,26 +255,43 @@ export async function waitForAnyProcessOutputToMatch( |
255 | 255 | return matchingProcess; |
256 | 256 | } |
257 | 257 |
|
258 | | -export async function killAllProcesses(signal = 'SIGTERM'): Promise<void> { |
| 258 | +/** |
| 259 | + * Kills a process by PID |
| 260 | + * @param pid The PID of the process to kill |
| 261 | + * @param signal The signal to send to the process |
| 262 | + */ |
| 263 | +async function killProcess(pid: number, signal: NodeJS.Signals): Promise<void> { |
| 264 | + if (process.platform === 'win32') { |
| 265 | + // /T kills child processes, /F forces it |
| 266 | + await new Promise<void>((resolve) => { |
| 267 | + child_process.exec(`taskkill /pid ${pid} /T /F`, () => resolve()); |
| 268 | + }); |
| 269 | + } else { |
| 270 | + // Use -pid to signal the entire process group |
| 271 | + try { |
| 272 | + process.kill(-pid, signal); |
| 273 | + } catch (error) { |
| 274 | + assertIsError(error); |
| 275 | + if (error.code !== 'ESRCH') { |
| 276 | + throw error; |
| 277 | + } |
| 278 | + } |
| 279 | + } |
| 280 | +} |
| 281 | + |
| 282 | +/** |
| 283 | + * Kills all tracked processes |
| 284 | + */ |
| 285 | +export async function killAllProcesses(signal: NodeJS.Signals = 'SIGTERM'): Promise<void> { |
259 | 286 | const processesToKill: Promise<void>[] = []; |
260 | 287 |
|
261 | 288 | while (_processes.length) { |
262 | 289 | const childProc = _processes.pop(); |
263 | | - if (!childProc || childProc.pid === undefined) { |
| 290 | + if (!childProc || childProc.pid === undefined || childProc.killed) { |
264 | 291 | continue; |
265 | 292 | } |
266 | 293 |
|
267 | | - processesToKill.push( |
268 | | - new Promise<void>((resolve) => { |
269 | | - treeKill(childProc.pid!, signal, () => { |
270 | | - // Ignore all errors. |
271 | | - // This is due to a race condition with the `waitForMatch` logic. |
272 | | - // where promises are resolved on matches and not when the process terminates. |
273 | | - // Also in some cases in windows we get `The operation attempted is not supported`. |
274 | | - resolve(); |
275 | | - }); |
276 | | - }), |
277 | | - ); |
| 294 | + processesToKill.push(killProcess(childProc.pid, signal)); |
278 | 295 | } |
279 | 296 |
|
280 | 297 | await Promise.all(processesToKill); |
|
0 commit comments