UNPKG

actionhero

Version:

actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks

332 lines (331 loc) 13.9 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.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; }