|
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 { setTimeout as sleep } from 'node:timers/promises'; |
| 4 | +import treeKill from 'tree-kill'; |
5 | 5 | import { delimiter, join, resolve } from 'node:path'; |
6 | 6 | import { stripVTControlCharacters, styleText } from 'node:util'; |
7 | 7 |
|
@@ -255,37 +255,29 @@ export async function waitForAnyProcessOutputToMatch( |
255 | 255 | return matchingProcess; |
256 | 256 | } |
257 | 257 |
|
258 | | -/** |
259 | | - * Kills all tracked processes with a retry mechanism. |
260 | | - */ |
261 | | -export async function killAllProcesses(signal: NodeJS.Signals = 'SIGTERM'): Promise<void> { |
262 | | - let attempts = 0; |
263 | | - const maxRetries = 3; |
264 | | - |
265 | | - while (_processes.length > 0 && attempts < maxRetries) { |
266 | | - attempts++; |
267 | | - |
268 | | - // Iterate backwards so we can remove elements while looping if needed. |
269 | | - for (let i = _processes.length - 1; i >= 0; i--) { |
270 | | - const childProc = _processes[i]; |
| 258 | +export async function killAllProcesses(signal = 'SIGTERM'): Promise<void> { |
| 259 | + const processesToKill: Promise<void>[] = []; |
271 | 260 |
|
272 | | - if (!childProc || childProc.killed) { |
273 | | - _processes.splice(i, 1); |
274 | | - continue; |
275 | | - } |
276 | | - |
277 | | - const killed = childProc.kill(signal); |
278 | | - if (killed) { |
279 | | - _processes.splice(i, 1); |
280 | | - continue; |
281 | | - } |
| 261 | + while (_processes.length) { |
| 262 | + const childProc = _processes.pop(); |
| 263 | + if (!childProc || childProc.pid === undefined) { |
| 264 | + continue; |
282 | 265 | } |
283 | 266 |
|
284 | | - // If still have processes, wait a bit before the next retry (e.g., 100ms) |
285 | | - if (_processes.length > 0 && attempts < maxRetries) { |
286 | | - await sleep(100); |
287 | | - } |
| 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 | + ); |
288 | 278 | } |
| 279 | + |
| 280 | + await Promise.all(processesToKill); |
289 | 281 | } |
290 | 282 |
|
291 | 283 | export function exec(cmd: string, ...args: string[]) { |
|
0 commit comments