Skip to content

Commit

Permalink
feat(js): improve @nx/js:node executor to be more resilient to many f…
Browse files Browse the repository at this point in the history
…ile change events
  • Loading branch information
jaysoo committed May 11, 2023
1 parent d0c6f5d commit efe04d6
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 187 deletions.
5 changes: 5 additions & 0 deletions docs/generated/packages/js/executors/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"type": "boolean",
"description": "Enable re-building when files change.",
"default": true
},
"debounce": {
"type": "number",
"description": "Delay in milliseconds to wait before restarting. Useful to batch multiple file changes events together. Set to zero (0) to disable.",
"default": 500
}
},
"additionalProperties": false,
Expand Down
1 change: 0 additions & 1 deletion packages/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"js-tokens": "^4.0.0",
"minimatch": "3.0.5",
"source-map-support": "0.5.19",
"tree-kill": "1.2.2",
"tslib": "^2.3.0",
"@nx/devkit": "file:../devkit",
"@nx/workspace": "file:../workspace"
Expand Down
129 changes: 129 additions & 0 deletions packages/js/src/executors/node/lib/kill-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Adapted from https://raw.githubusercontent.com/pkrumins/node-tree-kill/deee138/index.js
import { spawn, exec, ExecException } from 'child_process';

export async function killTree(pid: number, signal: NodeJS.Signals) {
const tree = {};
const pidsToProcess = {};
tree[pid] = [];
pidsToProcess[pid] = 1;

return new Promise<void>((resolve, reject) => {
const callback = (error: ExecException | null) => {
if (error) {
reject(error);
} else {
resolve();
}
};

switch (process.platform) {
case 'win32':
exec('taskkill /pid ' + pid + ' /T /F', callback);
break;
case 'darwin':
buildProcessTree(
pid,
tree,
pidsToProcess,
function (parentPid) {
return spawn('pgrep', ['-P', parentPid]);
},
function () {
killAll(tree, signal, callback);
}
);
break;
default: // Linux
buildProcessTree(
pid,
tree,
pidsToProcess,
function (parentPid) {
return spawn('ps', [
'-o',
'pid',
'--no-headers',
'--ppid',
parentPid,
]);
},
function () {
killAll(tree, signal, callback);
}
);
break;
}
});
}

function killAll(tree, signal, callback) {
const killed = {};
try {
Object.keys(tree).forEach(function (pid) {
tree[pid].forEach(function (pidpid) {
if (!killed[pidpid]) {
killPid(pidpid, signal);
killed[pidpid] = 1;
}
});
if (!killed[pid]) {
killPid(pid, signal);
killed[pid] = 1;
}
});
} catch (err) {
if (callback) {
return callback(err);
} else {
throw err;
}
}
if (callback) {
return callback();
}
}

function killPid(pid, signal) {
try {
process.kill(parseInt(pid, 10), signal);
} catch (err) {
if (err.code !== 'ESRCH') throw err;
}
}

function buildProcessTree(
parentPid,
tree,
pidsToProcess,
spawnChildProcessesList,
cb
) {
const ps = spawnChildProcessesList(parentPid);
let allData = '';
ps.stdout.on('data', (data) => {
data = data.toString('ascii');
allData += data;
});

const onClose = function (code) {
delete pidsToProcess[parentPid];

if (code != 0) {
// no more parent processes
if (Object.keys(pidsToProcess).length == 0) {
cb();
}
return;
}

allData.match(/\d+/g).forEach((_pid) => {
const pid = parseInt(_pid, 10);
tree[parentPid].push(pid);
tree[pid] = [];
pidsToProcess[pid] = 1;
buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb);
});
};

ps.on('close', onClose);
}
Loading

0 comments on commit efe04d6

Please sign in to comment.