actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
382 lines (381 loc) • 15.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Process = exports.fatalErrorCode = void 0;
const path = require("path");
const fs = require("fs");
const __1 = require("..");
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");
const config_1 = require("../modules/config");
const safeGlob_1 = require("../modules/utils/safeGlob");
exports.fatalErrorCode = "FATAL_ACTIONHERO_ERROR";
class Process {
constructor() {
this.initialized = false;
this.started = false;
this.stopped = false;
this.initializers = {};
this.loadInitializers = [];
this.startInitializers = [];
this.stopInitializers = [];
this.stopReasons = [];
this.startCount = 0;
index_1.api.process = this;
index_1.api.commands.initialize = this.initialize;
index_1.api.commands.start = this.start;
index_1.api.commands.stop = this.stop;
index_1.api.commands.restart = this.restart;
}
async initialize() {
const loadInitializerRankings = {};
const startInitializerRankings = {};
const stopInitializerRankings = {};
let initializerFiles = [];
// load initializers from core
initializerFiles = initializerFiles.concat((0, safeGlob_1.safeGlobSync)(path.join(__dirname, "..", "initializers", "**", "**/*(*.js|*.ts)")));
// load initializers from project
__1.config.general.paths.initializer.forEach((startPath) => {
initializerFiles = initializerFiles.concat((0, safeGlob_1.safeGlobSync)(path.join(startPath, "**", "**/*(*.js|*.ts)")));
});
// load initializers from plugins
for (const plugin of Object.values(__1.config.plugins)) {
const pluginPath = path.normalize(plugin.path);
if (!fs.existsSync(pluginPath)) {
throw new Error(`plugin path does not exist: ${pluginPath}`);
}
if (plugin.initializers !== false) {
// old style at the root of the project
initializerFiles = initializerFiles.concat((0, safeGlob_1.safeGlobSync)(path.join(pluginPath, "initializers", "**", "*.js")));
// new TS dist files
initializerFiles = initializerFiles.concat((0, safeGlob_1.safeGlobSync)(path.join(pluginPath, "dist", "initializers", "**", "*.js")));
}
}
initializerFiles = utils_1.utils.arrayUnique(initializerFiles);
initializerFiles = utils_1.utils.ensureNoTsHeaderOrSpecFiles(initializerFiles);
for (const i in initializerFiles) {
const f = initializerFiles[i];
const file = path.normalize(f);
if (require.cache[require.resolve(file)]) {
delete require.cache[require.resolve(file)];
}
let exportedClasses = await Promise.resolve(`${file}`).then(s => require(s));
// 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}`;
(0, log_1.log)(warningMessage, "warning");
}
else {
initializer.validate();
this.initializers[initializer.name] = initializer;
}
}
catch (error) {
this.fatalError(error, file);
}
function decorateInitError(error, type) {
var _a;
error["data"] = (_a = error["data"]) !== null && _a !== void 0 ? _a : {};
error["data"].name = initializer.name;
error["data"].file = file;
error["data"].type = type;
}
const initializeFunction = async () => {
if (typeof initializer.initialize === "function") {
(0, log_1.log)(`Loading initializer: ${initializer.name}`, "debug", file);
try {
await initializer.initialize();
(0, log_1.log)(`Loaded initializer: ${initializer.name}`, "debug", file);
}
catch (error) {
decorateInitError(error, "initialize");
throw error;
}
}
};
const startFunction = async () => {
if (typeof initializer.start === "function") {
(0, log_1.log)(`Starting initializer: ${initializer.name}`, "debug", file);
try {
await initializer.start();
(0, log_1.log)(`Started initializer: ${initializer.name}`, "debug", file);
}
catch (error) {
decorateInitError(error, "start");
throw error;
}
}
};
const stopFunction = async () => {
if (typeof initializer.stop === "function") {
(0, log_1.log)(`Stopping initializer: ${initializer.name}`, "debug", file);
try {
await initializer.stop();
(0, log_1.log)(`Stopped initializer: ${initializer.name}`, "debug", file);
}
catch (error) {
decorateInitError(error, "stop");
throw error;
}
}
};
if (!loadInitializerRankings[initializer.loadPriority]) {
loadInitializerRankings[initializer.loadPriority] = [];
}
if (!startInitializerRankings[initializer.startPriority]) {
startInitializerRankings[initializer.startPriority] = [];
}
if (!stopInitializerRankings[initializer.stopPriority]) {
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 {
for (const loader of this.loadInitializers)
await loader();
}
catch (error) {
return this.fatalError(error, "initialize");
}
this.initialized = true;
}
/**
* Start the Actionhero Process
*/
async start() {
if (!this.initialized)
await this.initialize();
const serverName = __1.config.general.serverName;
(0, pid_1.writePidFile)();
this.running = true;
index_1.api.running = true;
(0, log_1.log)(`environment: ${env_1.env}`, "notice");
(0, log_1.log)(`*** Starting ${serverName} ***`, "info");
this.startInitializers.push(async () => {
this.bootTime = new Date().getTime();
if (this.startCount === 0) {
(0, log_1.log)(`server ID: ${id_1.id}`, "notice");
(0, log_1.log)(`*** ${serverName} Started ***`, "notice");
this.startCount++;
}
else {
(0, log_1.log)(`*** ${serverName} Restarted ***`, "notice");
}
});
try {
for (const starter of this.startInitializers)
await starter();
}
catch (error) {
return this.fatalError(error, "start");
}
this.started = true;
}
/**
* Stop the Actionhero Process
*/
async stop(stopReasons = []) {
var _a;
const serverName = __1.config.general.serverName;
if (this.running) {
this.shuttingDown = true;
this.running = false;
this.initialized = false;
this.started = false;
this.stopReasons = Array.isArray(stopReasons)
? stopReasons
: [stopReasons];
(0, log_1.log)("stopping process...", "notice");
if (((_a = this.stopReasons) === null || _a === void 0 ? void 0 : _a.length) > 0) {
(0, log_1.log)(`stop reasons: ${this.stopReasons.join(", ")}`, "debug");
}
await utils_1.utils.sleep(100);
this.stopInitializers.push(async () => {
(0, pid_1.clearPidFile)();
(0, log_1.log)(`*** ${serverName} 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 {
for (const stopper of this.stopInitializers)
await stopper();
}
catch (error) {
return this.fatalError(error, "stop");
}
this.stopped = true;
}
else if (this.shuttingDown === true) {
// double sigterm; ignore it
}
else {
const message = `Cannot shut down ${serverName}, not running`;
(0, log_1.log)(message, "crit");
}
}
/**
* Restart the Actionhero Process
*/
async restart() {
if (this.running === true) {
await this.stop();
(0, config_1.rebuildConfig)();
await this.start();
}
else {
await this.start();
}
}
/**
* 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) => {
if (error.code !== exports.fatalErrorCode) {
if (index_1.api.exceptionHandlers) {
index_1.api.exceptionHandlers.report(error, "uncaught", "Exception", {}, "emerg");
}
else {
console.error(error);
}
}
if (this.shuttingDown !== true) {
let timer = awaitHardStop();
if (this.running)
await this.stop();
clearTimeout(timer);
stopCallback(1);
}
});
process.once("unhandledRejection", async (rejection) => {
if (rejection.code !== exports.fatalErrorCode) {
if (index_1.api.exceptionHandlers) {
index_1.api.exceptionHandlers.report(rejection, "uncaught", "Rejection", {}, "emerg");
}
else {
console.error(rejection);
}
}
if (!this.shuttingDown) {
let timer = awaitHardStop();
if (this.running)
await this.stop();
clearTimeout(timer);
stopCallback(1);
}
});
// handle signals
process.on("SIGINT", async () => {
(0, log_1.log)(`[ SIGNAL ] - SIGINT`, "notice");
let timer = awaitHardStop();
if (this.running)
await this.stop();
if (!this.shuttingDown) {
clearTimeout(timer);
stopCallback(0);
}
});
process.on("SIGTERM", async () => {
(0, log_1.log)(`[ SIGNAL ] - SIGTERM`, "notice");
let timer = awaitHardStop();
if (this.running)
await this.stop();
if (!this.shuttingDown) {
clearTimeout(timer);
stopCallback(0);
}
});
process.on("SIGUSR2", async () => {
(0, log_1.log)(`[ SIGNAL ] - SIGUSR2`, "notice");
let timer = awaitHardStop();
await this.restart();
clearTimeout(timer);
});
}
// HELPERS
async fatalError(errors = [], type) {
if (!(errors instanceof Array))
errors = [errors];
if (errors) {
const showStack = process.env.ACTIONHERO_FATAL_ERROR_STACK_DISPLAY
? process.env.ACTIONHERO_FATAL_ERROR_STACK_DISPLAY === "true"
: true;
errors.forEach((error) => {
if (!showStack)
delete error.stack;
if (index_1.api.exceptionHandlers) {
index_1.api.exceptionHandlers.report(error, "initializer", type);
}
else {
console.error(error);
}
});
if (this.running) {
await this.stop(errors.map((e) => { var _a; return (_a = e.message) !== null && _a !== void 0 ? _a : e.toString(); })); // stop and set the stopReasons
}
await utils_1.utils.sleep(100); // allow time for console.log to print
if (!errors[0].code)
errors[0].code = exports.fatalErrorCode;
throw errors[0];
}
}
flattenOrderedInitializer(collection) {
const output = [];
const keys = [];
for (const key in collection)
keys.push(parseInt(key, 10));
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;
}