Skip to content

Commit

Permalink
feat(utils/ps-manager): can kill processes for all children of a process
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed May 7, 2019
1 parent a929af4 commit 1157ff3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 57 deletions.
23 changes: 10 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@types/loglevel": "^1.5.4",
"@types/pify": "^3.0.2",
"@types/prompts": "^2.4.0",
"@types/ps-tree": "^1.1.0",
"@types/read-pkg-up": "^3.0.1",
"@types/semver": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^1.7.0",
Expand Down Expand Up @@ -130,6 +131,7 @@
"pify": "^4.0.1",
"promist": "^0.5.3",
"prompts": "^2.0.4",
"ps-tree": "^1.2.0",
"read-pkg-up": "^5.0.0",
"semver": "^6.0.0",
"slimconf": "^0.9.0",
Expand Down
28 changes: 10 additions & 18 deletions src/bin/attach.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import core from '~/core';
import { attach as _attach, options, resolver, add } from 'exits';
import manager from '~/utils/ps-manager';
import PSManager from '~/utils/ps-manager';
import logger from '~/utils/logger';
import { wait } from 'promist';
import { wait, status } from 'promist';

export default function attach(): void {
_attach();
Expand All @@ -25,25 +25,17 @@ export default function attach(): void {
}
});
add(async () => {
if (manager.isDone()) return;
logger.debug('Sending SIGTERM to all children processes');
const manager = new PSManager(process.pid);
if (!(await manager.hasChildren())) return;

let start = Date.now();
manager.kill('SIGTERM');
while (!manager.isDone() && Date.now() - start < 2500) {
await Promise.race([manager.promise(), wait(Date.now() - start)]);
}

if (manager.isDone()) return;
logger.debug('Seding SIGKILL to all children processes');
const term = status(manager.killAll('SIGTERM', 150));
await Promise.race([term, wait(3000)]);
if (term.status !== 'pending') return;

start = Date.now();
manager.kill('SIGKILL');
while (!manager.isDone() && Date.now() - start < 2500) {
await Promise.race([manager.promise(), wait(Date.now() - start)]);
}
const kill = status(manager.killAll('SIGKILL', 150));
await Promise.race([kill, wait(2000)]);
if (kill.status !== 'pending') return;

if (manager.isDone()) return;
logger.debug(
'Children processes have timed out without terminating. Exiting main process.'
);
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ export const CONCURRENTLY_PATH = require.resolve(
export const OWNED_ENV_KEY = 'kpo_owned';
export const GLOBALS_KEY = 'kpo_globals';
export type TEnvironmental = 'kpo_log';
export type TGlobal = 'version' | 'options' | 'processses';
export type TGlobal = 'version' | 'options';
56 changes: 31 additions & 25 deletions src/utils/ps-manager.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { globals } from '~/globals';
import { IOfType } from '~/types';
import tree from 'ps-tree';
import { waitUntil, lazy } from 'promist';
import logger from './logger';

const processes: IOfType<Promise<void>> = globals('processses', {}).get();

function remove(pid: number): void {
try {
delete processes[pid];
} catch (_) {
logger.error(`Removal of child process ${pid} failed`);
export default class PSManager {
private children: Promise<number[]>;
public constructor(pid: number) {
this.children = lazy((resolve, reject) => {
return tree(pid, (err, children) =>
err
? reject(err)
: resolve(children.map((child) => parseInt(child.PID)))
);
});
}
}
public async hasChildren(): Promise<boolean> {
return this.children.then((children) => !!children.length);
}
public async killAll(signal: string, interval?: number): Promise<void> {
const children = await this.children;
logger.debug(
`Seding ${signal} to all children processes: ${children.length}`
);

export default {
add(pid: number, promise: Promise<void>): void {
processes[pid] = promise.then(() => remove(pid)).catch(() => remove(pid));
},
kill(signal: string): void {
Object.keys(processes)
.map(Number)
.forEach((pid) => process.kill(pid, signal));
},
isDone(): boolean {
return !Object.keys(processes).length;
},
async promise(): Promise<void> {
await Promise.all(Object.values(processes));
return waitUntil(() => {
for (let pid of children) {
try {
process.kill(pid, 0);
// if it doesn't error out, it's still pending
return false;
} catch (_) {}
}
return true;
}, interval);
}
};
}

0 comments on commit 1157ff3

Please sign in to comment.