UNPKG

actionhero

Version:

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

355 lines (354 loc) 15.3 kB
"use strict"; 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.initialized = false; this.started = false; this.stopped = false; this.initializers = {}; this.loadInitializers = []; this.startInitializers = []; this.stopInitializers = []; this.stopReasons = []; 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); 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().then(() => 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 ${config.general.serverName} ***`, "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(`*** ${config.general.serverName} Started ***`, "notice"); this.startCount++; } else { log_1.log(`*** ${config.general.serverName} Restarted ***`, "notice"); } }); try { await utils_1.utils.asyncWaterfall(this.startInitializers); } catch (error) { return this.fatalError(error, "start"); } this.started = true; } async stop(stopReasons = []) { var _a; if (this.running) { this.shuttingDown = true; this.running = false; this.initialized = false; this.started = false; this.stopReasons = Array.isArray(stopReasons) ? stopReasons : [stopReasons]; log_1.log("stopping process...", "notice"); if (((_a = this.stopReasons) === null || _a === void 0 ? void 0 : _a.length) > 0) { log_1.log(`stop reasons: ${this.stopReasons.join(", ")}`, "debug"); } await utils_1.utils.sleep(100); this.stopInitializers.push(async () => { pid_1.clearPidFile(); log_1.log(`*** ${config.general.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 { await utils_1.utils.asyncWaterfall(this.stopInitializers); } 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 ${config.general.serverName}, 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(); if (!this.shuttingDown) { clearTimeout(timer); stopCallback(0); } }); process.on("SIGTERM", async () => { log_1.log(`[ SIGNAL ] - SIGTERM`, "notice"); let timer = awaitHardStop(); await this.stop(); if (!this.shuttingDown) { 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 instanceof Array)) errors = [errors]; if (errors) { log_1.log(`Error with initializer step: ${JSON.stringify(type)}`, "emerg"); const showStack = process.env.ACTIONHERO_FATAL_ERROR_STACK_DISPLAY ? process.env.ACTIONHERO_FATAL_ERROR_STACK_DISPLAY === "true" : true; errors.forEach((error) => { var _a, _b; log_1.log(showStack ? (_a = error.stack) !== null && _a !== void 0 ? _a : error.toString() : (_b = error.message) !== null && _b !== void 0 ? _b : error.toString(), "emerg"); }); await this.stop(errors.map((e) => { var _a; return (_a = e.message) !== null && _a !== void 0 ? _a : e.toString(); })); 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; }