actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
332 lines (331 loc) • 13.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Process = void 0;
const path = require("path");
const glob = require("glob");
const fs = require("fs");
const config_1 = require("./../modules/config");
const log_1 = require("../modules/log");
const utils_1 = require("../modules/utils");
const id_1 = require("./process/id");
const env_1 = require("./process/env");
const pid_1 = require("./process/pid");
const index_1 = require("../index");
let config = {};
class Process {
constructor() {
this.initializers = {};
this.loadInitializers = [];
this.startInitializers = [];
this.stopInitializers = [];
this.startCount = 0;
index_1.api.commands.initialize = async (...args) => {
return this.initialize(...args);
};
index_1.api.commands.start = async (...args) => {
return this.start(...args);
};
index_1.api.commands.stop = async () => {
return this.stop();
};
index_1.api.commands.restart = async () => {
return this.restart();
};
index_1.api.process = this;
}
async initialize(params = {}) {
this._startingParams = params;
const loadInitializerRankings = {};
const startInitializerRankings = {};
const stopInitializerRankings = {};
let initializerFiles = [];
// rebuild config with startingParams
config = config_1.buildConfig(this._startingParams);
// load initializers from core
initializerFiles = initializerFiles.concat(glob.sync(path.join(__dirname, "..", "initializers", "**", "**/*(*.js|*.ts)")));
// load initializers from project
config.general.paths.initializer.forEach((startPath) => {
initializerFiles = initializerFiles.concat(glob.sync(path.join(startPath, "**", "**/*(*.js|*.ts)")));
});
// load initializers from plugins
for (const pluginName in config.plugins) {
if (config.plugins[pluginName] !== false) {
const pluginPath = path.normalize(config.plugins[pluginName].path);
if (!fs.existsSync(pluginPath)) {
throw new Error(`plugin path does not exist: ${pluginPath}`);
}
// old style at the root of the project
initializerFiles = initializerFiles.concat(glob.sync(path.join(pluginPath, "initializers", "**", "*.js")));
// new TS dist files
initializerFiles = initializerFiles.concat(glob.sync(path.join(pluginPath, "dist", "initializers", "**", "*.js")));
}
}
initializerFiles = utils_1.utils.arrayUnique(initializerFiles);
initializerFiles = utils_1.utils.ensureNoTsHeaderFiles(initializerFiles);
initializerFiles.forEach((f) => {
const file = path.normalize(f);
if (require.cache[require.resolve(file)]) {
delete require.cache[require.resolve(file)];
}
let exportedClasses = require(file);
// allow for old-js style single default exports
if (typeof exportedClasses === "function") {
exportedClasses = { default: exportedClasses };
}
if (Object.keys(exportedClasses).length === 0) {
this.fatalError(new Error(`no exported initializers found in ${file}`), file);
}
for (const exportKey in exportedClasses) {
let initializer;
let InitializerClass = exportedClasses[exportKey];
try {
initializer = new InitializerClass();
// check if initializer already exists (exclude utils and config)
if (this.initializers[initializer.name]) {
const warningMessage = `an existing initializer with the same name \`${initializer.name}\` will be overridden by the file ${file}`;
log_1.log(warningMessage, "warning");
}
else {
initializer.validate();
this.initializers[initializer.name] = initializer;
}
}
catch (error) {
this.fatalError(error, file);
}
const initializeFunction = async () => {
if (typeof initializer.initialize === "function") {
log_1.log(`Loading initializer: ${initializer.name}`, "debug", file);
try {
await initializer.initialize(config);
try {
log_1.log(`Loaded initializer: ${initializer.name}`, "debug", file);
}
catch (e) { }
}
catch (error) {
const message = `Exception occurred in initializer \`${initializer.name}\` during load`;
try {
log_1.log(message, "emerg", error.toString());
}
catch (_error) {
console.error(message);
}
throw error;
}
}
};
const startFunction = async () => {
if (typeof initializer.start === "function") {
log_1.log(`Starting initializer: ${initializer.name}`, "debug", file);
try {
await initializer.start(config);
log_1.log(`Started initializer: ${initializer.name}`, "debug", file);
}
catch (error) {
log_1.log(`Exception occurred in initializer \`${initializer.name}\` during start`, "emerg", error.toString());
throw error;
}
}
};
const stopFunction = async () => {
if (typeof initializer.stop === "function") {
log_1.log(`Stopping initializer: ${initializer.name}`, "debug", file);
try {
await initializer.stop(config);
log_1.log(`Stopped initializer: ${initializer.name}`, "debug", file);
}
catch (error) {
log_1.log(`Exception occurred in initializer \`${initializer.name}\` during stop`, "emerg", error.toString());
throw error;
}
}
};
if (loadInitializerRankings[initializer.loadPriority] === undefined) {
loadInitializerRankings[initializer.loadPriority] = [];
}
if (startInitializerRankings[initializer.startPriority] === undefined) {
startInitializerRankings[initializer.startPriority] = [];
}
if (stopInitializerRankings[initializer.stopPriority] === undefined) {
stopInitializerRankings[initializer.stopPriority] = [];
}
if (initializer.loadPriority > 0) {
loadInitializerRankings[initializer.loadPriority].push(initializeFunction);
}
if (initializer.startPriority > 0) {
startInitializerRankings[initializer.startPriority].push(startFunction);
}
if (initializer.stopPriority > 0) {
stopInitializerRankings[initializer.stopPriority].push(stopFunction);
}
}
});
// flatten all the ordered initializer methods
this.loadInitializers = this.flattenOrderedInitializer(loadInitializerRankings);
this.startInitializers = this.flattenOrderedInitializer(startInitializerRankings);
this.stopInitializers = this.flattenOrderedInitializer(stopInitializerRankings);
try {
await utils_1.utils.asyncWaterfall(this.loadInitializers);
}
catch (error) {
return this.fatalError(error, "initialize");
}
this.initialized = true;
}
async start(params = {}) {
if (this.initialized !== true) {
await this.initialize(params);
}
pid_1.writePidFile();
this.running = true;
index_1.api.running = true;
log_1.log(`environment: ${env_1.env}`, "notice");
log_1.log("*** Starting Actionhero ***", "info");
this.startInitializers.push(() => {
this.bootTime = new Date().getTime();
if (this.startCount === 0) {
log_1.log(`server ID: ${id_1.id}`, "notice");
log_1.log("*** Actionhero Started ***", "notice");
this.startCount++;
}
else {
log_1.log("*** Actionhero Restarted ***", "notice");
}
});
try {
await utils_1.utils.asyncWaterfall(this.startInitializers);
}
catch (error) {
return this.fatalError(error, "start");
}
}
async stop() {
if (this.running) {
this.shuttingDown = true;
this.running = false;
this.initialized = false;
log_1.log("stopping process...", "notice");
await utils_1.utils.sleep(100);
this.stopInitializers.push(async () => {
pid_1.clearPidFile();
log_1.log("*** Actionhero Stopped ***", "notice");
delete this.shuttingDown;
// reset initializers to prevent duplicate check on restart
this.initializers = {};
index_1.api.running = false;
await utils_1.utils.sleep(100);
});
try {
await utils_1.utils.asyncWaterfall(this.stopInitializers);
}
catch (error) {
return this.fatalError(error, "stop");
}
}
else if (this.shuttingDown === true) {
// double sigterm; ignore it
}
else {
const message = "Cannot shut down actionhero, not running";
log_1.log(message, "crit");
}
}
async restart() {
if (this.running === true) {
await this.stop();
await this.start(this._startingParams);
}
else {
await this.start(this._startingParams);
}
}
/**
* Register listeners for process signals and uncaught exceptions & rejections.
* Try to gracefully shut down when signaled to do so
*/
registerProcessSignals(stopCallback = (exitCode) => { }) {
const timeout = process.env.ACTIONHERO_SHUTDOWN_TIMEOUT
? parseInt(process.env.ACTIONHERO_SHUTDOWN_TIMEOUT)
: 1000 * 30;
function awaitHardStop() {
return setTimeout(() => {
console.error(`Process did not terminate within ${timeout}ms. Stopping now!`);
process.nextTick(process.exit(1));
}, timeout);
}
// handle errors & rejections
process.once("uncaughtException", async (error) => {
log_1.log(`UNCAUGHT EXCEPTION: ` + error.stack, "fatal");
if (!this.shuttingDown === true) {
let timer = awaitHardStop();
await this.stop();
clearTimeout(timer);
stopCallback(1);
}
});
process.once("unhandledRejection", async (rejection) => {
log_1.log(`UNHANDLED REJECTION: ` + rejection.stack, "fatal");
if (!this.shuttingDown === true) {
let timer = awaitHardStop();
await this.stop();
clearTimeout(timer);
stopCallback(1);
}
});
// handle signals
process.on("SIGINT", async () => {
log_1.log(`[ SIGNAL ] - SIGINT`, "notice");
let timer = awaitHardStop();
await this.stop();
clearTimeout(timer);
stopCallback(0);
});
process.on("SIGTERM", async () => {
log_1.log(`[ SIGNAL ] - SIGTERM`, "notice");
let timer = awaitHardStop();
await this.stop();
clearTimeout(timer);
stopCallback(0);
});
process.on("SIGUSR2", async () => {
log_1.log(`[ SIGNAL ] - SIGUSR2`, "notice");
let timer = awaitHardStop();
await this.restart();
clearTimeout(timer);
});
}
// HELPERS
async fatalError(errors, type) {
if (errors && !(errors instanceof Array)) {
errors = [errors];
}
if (errors) {
log_1.log(`Error with initializer step: ${JSON.stringify(type)}`, "emerg");
errors.forEach((error) => {
log_1.log(error.stack, "emerg");
});
await this.stop();
await utils_1.utils.sleep(1000); // allow time for console.log to print
process.exit(1);
}
}
flattenOrderedInitializer(collection) {
const output = [];
const keys = [];
for (const key in collection) {
keys.push(parseInt(key));
}
keys.sort(sortNumber);
keys.forEach((key) => {
collection[key].forEach((d) => {
output.push(d);
});
});
return output;
}
}
exports.Process = Process;
function sortNumber(a, b) {
return a - b;
}