diff --git a/package.json b/package.json index 4573508..4ad0b13 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "stylelint-config-recommended-scss": "^16.0.0", "tiktoken": "^1.0.22", "tinyglobby": "^0.2.14", + "tree-kill": "^1.2.2", "tsx": "^4.20.3", "typescript": "^5.8.3", "yargs": "^18.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 785b57a..ab2e5b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: tinyglobby: specifier: ^0.2.14 version: 0.2.15 + tree-kill: + specifier: ^1.2.2 + version: 1.2.2 tsx: specifier: ^4.20.3 version: 4.20.5 @@ -6033,6 +6036,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -13567,6 +13574,8 @@ snapshots: tr46@0.0.3: {} + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} triple-beam@1.4.1: {} diff --git a/runner/orchestration/generate.ts b/runner/orchestration/generate.ts index 2322aa3..90104db 100644 --- a/runner/orchestration/generate.ts +++ b/runner/orchestration/generate.ts @@ -158,7 +158,7 @@ export async function generateCodeAndAssess(options: { ? // Building can be really expensive. We likely should add support for "CPU hints" per environment. // E.g. CLI building is really CPU intensive with ESBuild being multi-core. // TODO: Follow-up on this and add CPU hints. - Math.floor(availableParallelism() * 0.4 * 0.5) + Math.floor(appConcurrency * 0.5) : Infinity, }); diff --git a/runner/utils/kill-gracefully.ts b/runner/utils/kill-gracefully.ts index 195b707..b922c11 100644 --- a/runner/utils/kill-gracefully.ts +++ b/runner/utils/kill-gracefully.ts @@ -1,13 +1,31 @@ import { ChildProcess } from 'child_process'; +import treeKill from 'tree-kill'; + +function treeKillPromise(pid: number, signal: string): Promise { + return new Promise((resolve, reject) => { + treeKill(pid, signal, (err) => { + if (err !== undefined) { + reject(err); + } else { + resolve(err); + } + }); + }); +} export function killChildProcessGracefully( child: ChildProcess, timeoutInMs = 1000 * 10 // 10s ): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // Process already exited. if (child.exitCode !== null) { resolve(); + return; + } + const pid = child.pid; + if (pid === undefined) { + throw new Error(`No process ID for processed that should be killed.`); } // Watch for exiting, so that we can cancel the timeout(s) then. @@ -18,10 +36,17 @@ export function killChildProcessGracefully( }); // Send SIGTERM - child.kill('SIGTERM'); + try { + await treeKillPromise(pid, 'SIGTERM'); + } catch (e) { + console.error( + `Could not send "SIGTERM" for killing process. Trying "SIGKILL".` + ); + } + // Start a timeout for the SIGKILL fallback const sigkillTimeoutId = setTimeout( - () => child.kill('SIGKILL'), + () => treeKill(pid, 'SIGKILL'), timeoutInMs ); // Start another timeout to reject the promise if the child process never fires `exit` for some reasons.