pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
156 lines • 6.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const const_1 = require("../const");
const path_1 = require("path");
const pandora_dollar_1 = require("pandora-dollar");
const pathProcessBootstrap = require.resolve('./ProcessBootstrap');
/**
* Class ApplicationHandler
*/
class ProcessHandler {
// TODO: make nodejsStdout is required
constructor(processRepresentation) {
this.startCount = 0;
this.state = const_1.State.pending;
this.processRepresentation = processRepresentation;
}
get appName() {
return this.processRepresentation.appName;
}
get processName() {
return this.processRepresentation.processName;
}
get appDir() {
return this.processRepresentation.appDir;
}
get pid() {
return this.forkedProcess && this.forkedProcess.pid;
}
/**
* Start application through fork
* @return {Promise<void>}
*/
async start() {
const args = ['--params', JSON.stringify(this.processRepresentation)]
.concat(this.processRepresentation.args || []);
this.startCount++;
await this.doFork(args);
}
doFork(args) {
const representation = this.processRepresentation;
const execArgv = process.execArgv.slice(0);
// Handing typeScript file,just for testing
if (/\.ts$/.test(module.filename) && execArgv.indexOf('ts-node/register') === -1) {
execArgv.push('-r', 'ts-node/register', '-r', 'nyc-ts-patch');
}
const userExecArgv = representation.execArgv;
if (userExecArgv && userExecArgv.length) {
execArgv.push.apply(execArgv, userExecArgv);
}
const env = Object.assign(Object.assign(Object.assign({ [const_1.PANDORA_HOME]: path_1.join(__dirname, '../../') }, process.env), representation.env), { [const_1.PANDORA_CWD]: process.cwd(),
// require.main === module maybe be 'false' after patched spawn wrap in mocha ??? not very sure, but keep it for safe
RUN_PROCESS_BOOTSTRAP_BY_FORCE: true });
return new Promise((resolve, reject) => {
// Fork it
const forkedProcess = child_process_1.fork(pathProcessBootstrap, args, {
cwd: representation.appDir,
execArgv,
detached: true,
stdio: ['ignore', process.stdout, process.stderr, 'ipc'],
env
});
forkedProcess.once('message', (message) => {
if (message.action === const_1.PROCESS_READY) {
const msg = `Process [name = ${this.processRepresentation.processName}, pid = ${forkedProcess.pid}] Started successfully!`;
this.state = const_1.State.complete;
pandora_dollar_1.consoleLogger.info(msg);
resolve();
}
else if (message.action === const_1.PROCESS_ERROR) {
this.stop().catch((err) => {
const msg = `Process [name = ${this.processRepresentation.processName}, pid = ${forkedProcess.pid}] Start error!`;
pandora_dollar_1.consoleLogger.error(err);
pandora_dollar_1.consoleLogger.error(msg);
reject(new Error(msg));
});
}
});
// Here just to distinguish normal exits and exceptional exits, exceptional exits need to restart
forkedProcess.once('exit', (code, signal) => {
const msg = `Process [name = ${this.processRepresentation.processName}, pid = ${forkedProcess.pid}] Exit with code ${code} and signal ${signal}`;
pandora_dollar_1.consoleLogger.info(msg);
switch (this.state) {
case const_1.State.complete:
// Restart it automatically when it exceptional exits after it start successful
this.start().catch(err => {
pandora_dollar_1.consoleLogger.error('Restart application error');
pandora_dollar_1.consoleLogger.error(err);
});
break;
case const_1.State.pending:
default:
const err = new Error(`Start failed! Run command [ pandora log ${this.appName} ] to get more information`);
reject(err);
break;
}
});
this.forkedProcess = forkedProcess;
});
}
/**
* Stop application through kill
* @return {Promise<void>}
*/
stop() {
if (this.state === const_1.State.stopped) {
return Promise.resolve();
}
this.state = const_1.State.stopped;
const forkedProcess = this.forkedProcess;
this.forkedProcess = null;
return new Promise((resolve) => {
const timer = setTimeout(() => {
forkedProcess.kill('SIGKILL');
setTimeout(resolve, 2000);
}, const_1.SHUTDOWN_TIMEOUT);
forkedProcess.once('exit', () => {
clearTimeout(timer);
resolve();
});
forkedProcess.kill('SIGTERM');
});
}
/**
* Reload application through process message
* @param processName
* @return {Promise<void>}
*/
reload(processName) {
if (processName !== this.processName && processName != null) {
return;
}
if (this.processRepresentation.scale === 1) {
return this.stop().then(this.start.bind(this));
}
return new Promise((resolve, reject) => {
this.forkedProcess.once('message', (message) => {
if (message.action === const_1.RELOAD_SUCCESS) {
resolve();
}
if (message.action === const_1.RELOAD_ERROR) {
reject(message.error);
}
});
this.forkedProcess.send({
action: const_1.RELOAD,
name: processName,
});
setTimeout(() => {
reject(new Error('Reload Timeout'));
}, const_1.RELOAD_TIMEOUT);
});
}
}
exports.ProcessHandler = ProcessHandler;
//# sourceMappingURL=ProcessHandler.js.map