UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

382 lines (381 loc) 15.9 kB
"use strict"; 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; }