Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: handle child processes #3

Merged
merged 11 commits into from
Apr 21, 2022
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# @comet/dev-process-manager

## Usage
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
},
"devDependencies": {
"@comet/eslint-config": "^0.0.1-canary.8102.6588",
"typescript": "^4.5.5",
"eslint": "^7.0.0",
"ts-loader": "^8.0.0",
"eslint": "^7.0.0"
"typescript": "^4.5.5"
},
"engines": {
"node": "14"
Expand Down
120 changes: 102 additions & 18 deletions src/commands/start.command.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { spawn, ChildProcess } from "child_process";
import { Socket, createServer, createConnection } from "net";
import { AppDefinition } from "../app-definition.type";
import { unlinkSync } from "fs";

export const start = async (apps: AppDefinition[]) => {
export const start = async (pmConfigFilePath: string) => {
const { apps }: { apps: AppDefinition[] } = await import(`${process.cwd()}/${pmConfigFilePath}`);
const processes: { [key: string]: ChildProcess } = {};
const logSockets: { socket: Socket, name: string | null; }[] = [];
let shuttingDown = false;
Expand All @@ -14,15 +16,15 @@ export const start = async (apps: AppDefinition[]) => {
process.stdout.write(data);
logSockets.forEach(s => {
if (!s.name || s.name == app.name) {
s.socket.write(data);
s.socket.write(`${s.name}: ${data}`);
}
});
});
p.stderr.on('data', data => {
process.stderr.write(data);
logSockets.forEach(s => {
if (!s.name || s.name == app.name) {
s.socket.write(data);
s.socket.write(`${s.name}: ${data}`);
}
});
});
Expand All @@ -40,6 +42,10 @@ export const start = async (apps: AppDefinition[]) => {
processes[app.name] = p;
}

try {
unlinkSync("./.pm.sock");
Fabian-Fynn marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) { }

const server = createServer();
server.listen(".pm.sock");
server.on('connection', (s) => {
Expand Down Expand Up @@ -97,13 +103,7 @@ export const start = async (apps: AppDefinition[]) => {
s.write(JSON.stringify(response));
s.end();
} else if (cmd == "shutdown") {
console.log("shutting down");
shuttingDown = true;
Object.values(processes).forEach(p => {
if (!p.killed) p.kill("SIGINT");
})
server.close();
process.exit();
shutdown(s);
} else {
console.error("Unknown command", cmd);
}
Expand All @@ -115,15 +115,99 @@ export const start = async (apps: AppDefinition[]) => {
});

process.on("SIGINT", function () {
console.log("shutting down")
server.close();
shutdown();
});

process.on("SIGTERM", function () {
shutdown();
});

const events = ["beforeExit", "disconnected", "message", "rejectionHandled", "uncaughtException", "exit", "SIGABRT", "SIGHUP", "SIGPWR", "SIGQUIT"];

events.forEach((eventName) => {
process.on(eventName, (...args) => {
console.log('Unhandled error event ' + eventName + ' was called with args : ' + args.join(','));
shutdown();
});
});


const shutdown = async (s?: Socket) => {
console.log("shutting down");
shuttingDown = true;
for (const name in processes) {
const p = processes[name];
if (!p.killed) {
console.log("killing " + name);
p.kill("SIGINT");
await Promise.all(Object.values(processes).map(async p => {
if (p.pid) {
const list: { [key: string]: string[] } = await getChildProcesses(p.pid.toString(), { [p.pid]: [] }, { [p.pid]: 1 });
killProcesses(list);
}
}));
server.close();
s?.destroy();
process.exit();
}

const killProcesses = (tree: { [key: string]: string[] }) => {
const killed: { [key: string]: number } = {};
Object.keys(tree).map((pid) => {
tree[pid].map((childPid) => {
if (!killed[childPid]) {
killPid(childPid);
killed[childPid] = 1;
}
if (!killed[pid]) {
killPid(pid);
killed[pid] = 1;
}
})
})
return;
}

const killPid = (pid: string) => {
try {
process.kill(parseInt(pid, 10), "SIGINT");
} catch (err) {
console.error(err);
}
});
}

const getChildProcesses = async (parentPid: string, tree: { [key: string]: string[] }, pidsToProcess: { [key: string]: number }): Promise<{ [key: string]: string[] }> => {
return new Promise((resolve, reject) => {
const ps = spawn('pgrep', ['-P', parentPid]);
Fabian-Fynn marked this conversation as resolved.
Show resolved Hide resolved
let allData = "";

const onClose = (code: unknown) => {
delete pidsToProcess[parentPid];
if (code != 0) {
if (Object.keys(pidsToProcess).length === 0) {
resolve(tree);
}
return tree;
}

const pids = allData.match(/\d+/g) || [];
if (pids.length === 0) {
return resolve(tree);
}
pids.forEach(function (pid) {
tree[parentPid].push(pid);
tree[pid] = [];
pidsToProcess[pid] = 1;
resolve(getChildProcesses(pid, tree, pidsToProcess));
})
};

ps.on('error', function (err) {
console.error(err);
reject(err);
});

ps.stdout.on('data', function (data) {
data = data.toString('ascii');
allData += data;
})

ps.on('close', onClose);
})
}
}
12 changes: 0 additions & 12 deletions src/ecosystem.config.ts

This file was deleted.

7 changes: 3 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Command } from 'commander';
import { start, shutdown, status, logs, restart } from "./commands";
import eco from "./ecosystem.config";

const program = new Command();
program.command("start")
.action(() => {
start(eco.apps);
program.command("start <pmConfigFilePath>")
.action((pmConfigFilePath) => {
start(pmConfigFilePath);
});

program.command("logs [name]")
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"strict": true,
"strictPropertyInitialization": false,
"strictNullChecks": true,
"resolveJsonModule": true,
},
"include": [
"src"
Expand Down