Skip to content
Snippets Groups Projects
Unverified Commit 21ab4bad authored by Gabriel-Ladzaretti's avatar Gabriel-Ladzaretti Committed by GitHub
Browse files

feat(util/exec): enable process group handling on process termination (#17447)

parent dafda2e5
No related branches found
Tags 38.115.0
No related merge requests found
...@@ -58,3 +58,7 @@ If set, Renovate will enable `forcePathStyle` when instantiating the AWS s3 clie ...@@ -58,3 +58,7 @@ If set, Renovate will enable `forcePathStyle` when instantiating the AWS s3 clie
> Whether to force path style URLs for S3 objects (e.g., `https://s3.amazonaws.com//` instead of `https://.s3.amazonaws.com/` > Whether to force path style URLs for S3 objects (e.g., `https://s3.amazonaws.com//` instead of `https://.s3.amazonaws.com/`
Source: [AWS s3 documentation - Interface BucketEndpointInputConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/bucketendpointinputconfig.html) Source: [AWS s3 documentation - Interface BucketEndpointInputConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/bucketendpointinputconfig.html)
## `RENOVATE_X_EXEC_GPID_HANDLE`
If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate.
...@@ -33,6 +33,7 @@ interface StubArgs { ...@@ -33,6 +33,7 @@ interface StubArgs {
stdout?: string; stdout?: string;
stderr?: string; stderr?: string;
timeout?: number; timeout?: number;
pid?: number;
} }
function getReadable( function getReadable(
...@@ -66,6 +67,7 @@ function getSpawnStub(args: StubArgs): ChildProcess { ...@@ -66,6 +67,7 @@ function getSpawnStub(args: StubArgs): ChildProcess {
stderr, stderr,
encoding, encoding,
timeout, timeout,
pid = 31415,
} = args; } = args;
const listeners: Events = {}; const listeners: Events = {};
...@@ -140,6 +142,7 @@ function getSpawnStub(args: StubArgs): ChildProcess { ...@@ -140,6 +142,7 @@ function getSpawnStub(args: StubArgs): ChildProcess {
emit, emit,
unref, unref,
kill, kill,
pid,
} as ChildProcess; } as ChildProcess;
} }
...@@ -282,4 +285,48 @@ describe('util/exec/common', () => { ...@@ -282,4 +285,48 @@ describe('util/exec/common', () => {
}); });
}); });
}); });
describe('handle gpid', () => {
const killSpy = jest.spyOn(process, 'kill');
afterEach(() => {
delete process.env.RENOVATE_X_EXEC_GPID_HANDLE;
jest.restoreAllMocks();
});
it('calls process.kill on the gpid', async () => {
process.env.RENOVATE_X_EXEC_GPID_HANDLE = 'true';
const cmd = 'ls -l';
const exitSignal = 'SIGTERM';
const stub = getSpawnStub({ cmd, exitCode: null, exitSignal });
spawn.mockImplementationOnce((cmd, opts) => stub);
killSpy.mockImplementationOnce((pid, signal) => true);
await expect(
exec(cmd, partial<RawExecOptions>({ encoding: 'utf8' }))
).rejects.toMatchObject({
cmd,
signal: exitSignal,
message: `Command failed: ${cmd}\nInterrupted by ${exitSignal}`,
});
expect(process.kill).toHaveBeenCalledWith(-stub.pid!, exitSignal);
});
it('handles process.kill call on non existent gpid', async () => {
process.env.RENOVATE_X_EXEC_GPID_HANDLE = 'true';
const cmd = 'ls -l';
const exitSignal = 'SIGTERM';
const stub = getSpawnStub({ cmd, exitCode: null, exitSignal });
spawn.mockImplementationOnce((cmd, opts) => stub);
killSpy.mockImplementationOnce((pid, signal) => {
throw new Error();
});
await expect(
exec(cmd, partial<RawExecOptions>({ encoding: 'utf8' }))
).rejects.toMatchObject({
cmd,
signal: exitSignal,
message: `Command failed: ${cmd}\nInterrupted by ${exitSignal}`,
});
});
});
}); });
...@@ -122,25 +122,25 @@ export function exec(cmd: string, opts: RawExecOptions): Promise<ExecResult> { ...@@ -122,25 +122,25 @@ export function exec(cmd: string, opts: RawExecOptions): Promise<ExecResult> {
function kill(cp: ChildProcess, signal: NodeJS.Signals): boolean { function kill(cp: ChildProcess, signal: NodeJS.Signals): boolean {
try { try {
// TODO: will be enabled in #16654 if (cp.pid && process.env.RENOVATE_X_EXEC_GPID_HANDLE) {
/** /**
* If `pid` is negative, but not `-1`, signal shall be sent to all processes * If `pid` is negative, but not `-1`, signal shall be sent to all processes
* (excluding an unspecified set of system processes), * (excluding an unspecified set of system processes),
* whose process group ID (pgid) is equal to the absolute value of pid, * whose process group ID (pgid) is equal to the absolute value of pid,
* and for which the process has permission to send a signal. * and for which the process has permission to send a signal.
*/ */
// process.kill(-(cp.pid as number), signal); return process.kill(-cp.pid, signal);
} else {
// destroying stdio is needed for unref to work // destroying stdio is needed for unref to work
// https://nodejs.org/api/child_process.html#subprocessunref // https://nodejs.org/api/child_process.html#subprocessunref
// https://github.com/nodejs/node/blob/4d5ff25a813fd18939c9f76b17e36291e3ea15c3/lib/child_process.js#L412-L426 // https://github.com/nodejs/node/blob/4d5ff25a813fd18939c9f76b17e36291e3ea15c3/lib/child_process.js#L412-L426
cp.stderr?.destroy(); cp.stderr?.destroy();
cp.stdout?.destroy(); cp.stdout?.destroy();
cp.unref(); cp.unref();
return cp.kill(signal); return cp.kill(signal);
}
} catch (err) { } catch (err) {
// cp is a single node tree, therefore -pid is invalid as there is no such pgid, // cp is a single node tree, therefore -pid is invalid as there is no such pgid,
// istanbul ignore next: will be covered once we use process.kill
return false; return false;
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment