Skip to content

Commit 151129a

Browse files
committed
fix: ensure spawned processes are fully killed
Ensures that spawned processes are fully killed.
1 parent 6cf046d commit 151129a

File tree

4 files changed

+39
-4
lines changed

4 files changed

+39
-4
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"stylelint-config-recommended-scss": "^16.0.0",
8484
"tiktoken": "^1.0.22",
8585
"tinyglobby": "^0.2.14",
86+
"tree-kill": "^1.2.2",
8687
"tsx": "^4.20.3",
8788
"typescript": "^5.8.3",
8889
"yargs": "^18.0.0",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runner/orchestration/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export async function generateCodeAndAssess(options: {
158158
? // Building can be really expensive. We likely should add support for "CPU hints" per environment.
159159
// E.g. CLI building is really CPU intensive with ESBuild being multi-core.
160160
// TODO: Follow-up on this and add CPU hints.
161-
Math.floor(availableParallelism() * 0.4 * 0.5)
161+
Math.floor(appConcurrency * 0.5)
162162
: Infinity,
163163
});
164164

runner/utils/kill-gracefully.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import { ChildProcess } from 'child_process';
2+
import treeKill from 'tree-kill';
3+
4+
function treeKillPromise(pid: number, signal: string): Promise<void> {
5+
return new Promise((resolve, reject) => {
6+
treeKill(pid, signal, (err) => {
7+
if (err !== undefined) {
8+
reject(err);
9+
} else {
10+
resolve(err);
11+
}
12+
});
13+
});
14+
}
215

316
export function killChildProcessGracefully(
417
child: ChildProcess,
518
timeoutInMs = 1000 * 10 // 10s
619
): Promise<void> {
7-
return new Promise((resolve, reject) => {
20+
return new Promise(async (resolve, reject) => {
821
// Process already exited.
922
if (child.exitCode !== null) {
1023
resolve();
24+
return;
25+
}
26+
const pid = child.pid;
27+
if (pid === undefined) {
28+
throw new Error(`No process ID for processed that should be killed.`);
1129
}
1230

1331
// Watch for exiting, so that we can cancel the timeout(s) then.
@@ -18,10 +36,17 @@ export function killChildProcessGracefully(
1836
});
1937

2038
// Send SIGTERM
21-
child.kill('SIGTERM');
39+
try {
40+
await treeKillPromise(pid, 'SIGTERM');
41+
} catch (e) {
42+
console.error(
43+
`Could not send "SIGTERM" for killing process. Trying "SIGKILL".`
44+
);
45+
}
46+
2247
// Start a timeout for the SIGKILL fallback
2348
const sigkillTimeoutId = setTimeout(
24-
() => child.kill('SIGKILL'),
49+
() => treeKill(pid, 'SIGKILL'),
2550
timeoutInMs
2651
);
2752
// Start another timeout to reject the promise if the child process never fires `exit` for some reasons.

0 commit comments

Comments
 (0)