diff --git a/src/platforms/ios/runner-session.ts b/src/platforms/ios/runner-session.ts index 15ce285a..663e5788 100644 --- a/src/platforms/ios/runner-session.ts +++ b/src/platforms/ios/runner-session.ts @@ -6,7 +6,7 @@ import { type ExecBackgroundResult, } from '../../utils/exec.ts'; import { withKeyedLock } from '../../utils/keyed-lock.ts'; -import { isProcessAlive } from '../../utils/process-identity.ts'; +import { isProcessAlive, isProcessGroupAlive } from '../../utils/process-identity.ts'; import type { DeviceInfo } from '../../utils/device.ts'; import { buildSimctlArgsForDevice } from './simctl.ts'; import { @@ -192,7 +192,9 @@ async function stopRunnerSessionInternal( new Promise((resolve) => setTimeout(resolve, RUNNER_STOP_WAIT_TIMEOUT_MS)), ]); } catch {} - await killRunnerProcessTree(session.child.pid, 'SIGKILL'); + if (isRunnerProcessTreeAlive(session.child.pid)) { + await killRunnerProcessTree(session.child.pid, 'SIGKILL'); + } cleanupTempFile(session.xctestrunPath); cleanupTempFile(session.jsonPath); await session.simulatorSetRedirect?.release(); @@ -274,6 +276,11 @@ function isRunnerProcessAlive(pid: number | undefined): boolean { return isProcessAlive(pid); } +function isRunnerProcessTreeAlive(pid: number | undefined): boolean { + if (!pid) return false; + return isRunnerProcessAlive(pid) || isProcessGroupAlive(pid); +} + async function killRunnerProcessTree( pid: number | undefined, signal: 'SIGINT' | 'SIGTERM' | 'SIGKILL', diff --git a/src/utils/__tests__/process-identity.test.ts b/src/utils/__tests__/process-identity.test.ts index 8726ce9d..9b434991 100644 --- a/src/utils/__tests__/process-identity.test.ts +++ b/src/utils/__tests__/process-identity.test.ts @@ -3,6 +3,7 @@ import assert from 'node:assert/strict'; import { isAgentDeviceDaemonCommand, isProcessAlive, + isProcessGroupAlive, readProcessStartTime, readProcessCommand, } from '../process-identity.ts'; @@ -11,6 +12,10 @@ test('isProcessAlive returns false for invalid pid', () => { assert.equal(isProcessAlive(-1), false); }); +test('isProcessGroupAlive returns false for invalid pid', () => { + assert.equal(isProcessGroupAlive(-1), false); +}); + test('readProcessStartTime returns value for current process', () => { const startTime = readProcessStartTime(process.pid); if (startTime === null) { diff --git a/src/utils/process-identity.ts b/src/utils/process-identity.ts index cdd9c518..91cf9ba5 100644 --- a/src/utils/process-identity.ts +++ b/src/utils/process-identity.ts @@ -18,6 +18,16 @@ export function isProcessAlive(pid: number): boolean { } } +export function isProcessGroupAlive(pid: number): boolean { + if (!Number.isInteger(pid) || pid <= 0) return false; + try { + process.kill(-pid, 0); + return true; + } catch (err) { + return (err as NodeJS.ErrnoException).code === 'EPERM'; + } +} + export function readProcessStartTime(pid: number): string | null { if (!Number.isInteger(pid) || pid <= 0) return null; try {