UNPKG

container.ts

Version:
315 lines 15.1 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); var child_process_1 = require("child_process"); var lodash_1 = require("lodash"); var path_1 = require("path"); var rxjs_1 = require("rxjs"); var operators_1 = require("rxjs/operators"); var container_1 = require("../../../container"); var error_chain_1 = require("../../error/error-chain"); var validate_1 = require("../validate"); var process_1 = require("./process"); /** Scripts log names. */ var EScriptsLog; (function (EScriptsLog) { EScriptsLog["Information"] = "Scripts.Information"; })(EScriptsLog = exports.EScriptsLog || (exports.EScriptsLog = {})); /** ScriptsProcess error codes. */ var EScriptsProcessError; (function (EScriptsProcessError) { EScriptsProcessError["Exit"] = "ScriptsProcessError.Exit"; EScriptsProcessError["Error"] = "ScriptsProcessError.Error"; })(EScriptsProcessError = exports.EScriptsProcessError || (exports.EScriptsProcessError = {})); /** ScriptsProcess error class. */ var ScriptsProcessError = /** @class */ (function (_super) { __extends(ScriptsProcessError, _super); function ScriptsProcessError(code, cause, context) { return _super.call(this, { name: "ScriptsProcessError", value: __assign({ code: code }, context) }, cause) || this; } return ScriptsProcessError; }(error_chain_1.ErrorChain)); exports.ScriptsProcessError = ScriptsProcessError; /** Spawned scripts process. */ var ScriptsProcess = /** @class */ (function () { function ScriptsProcess(scripts, fileName, process) { var _this = this; this.scripts = scripts; this.fileName = fileName; this.process = process; // Connect process events to subjects. this.process.on("exit", function (code, signal) { _this.scripts.processExit$.next({ pid: _this.process.pid, code: code, signal: signal }); }); this.process.on("error", function (error) { _this.scripts.processError$.next({ pid: _this.process.pid, error: error }); }); // Create exit observable, reduce code/signal argument. this.exit$ = this.scripts.rxTakeUntilModuleDown(this.scripts.processExit$).pipe(operators_1.filter(function (exit) { return exit.pid === _this.process.pid; }), operators_1.take(1), operators_1.map(function (exit) { var code = exit.code != null ? exit.code : exit.signal; return code != null ? code : 1; })); // Subscribe to exit observable to log errors. this.exit$.subscribe(function (code) { if (code !== 0) { var error = new ScriptsProcessError(EScriptsProcessError.Exit, undefined, { code: code, fileName: _this.fileName }); _this.scripts.log.error(error, { code: code }); } }); // Subscribe to error observable to forward to scripts logger. this.scripts .rxTakeUntilModuleDown(this.scripts.processError$) .pipe(operators_1.takeUntil(this.exit$), operators_1.filter(function (exit) { return exit.pid === _this.process.pid; })) .subscribe(function (error) { var chained = new ScriptsProcessError(EScriptsProcessError.Error, error.error, { fileName: _this.fileName }); _this.scripts.log.error(chained); }); } /** End child process with signal. */ ScriptsProcess.prototype.kill = function (signal) { this.process.kill(signal); return this.exit$; }; return ScriptsProcess; }()); exports.ScriptsProcess = ScriptsProcess; /** Scripts environment variable names. */ var EScriptsEnv; (function (EScriptsEnv) { /** Scripts directory path (required). */ EScriptsEnv["Path"] = "SCRIPTS_PATH"; })(EScriptsEnv = exports.EScriptsEnv || (exports.EScriptsEnv = {})); /** Scripts log names. */ (function (EScriptsLog) { EScriptsLog["WorkerStart"] = "Scripts.WorkerStart"; EScriptsLog["WorkerStop"] = "Scripts.WorkerStop"; EScriptsLog["WorkerExit"] = "Scripts.WorkerExit"; EScriptsLog["WorkerRestart"] = "Scripts.WorkerRestart"; EScriptsLog["WorkerRestartLimit"] = "Scripts.WorkerRestartLimit"; })(EScriptsLog = exports.EScriptsLog || (exports.EScriptsLog = {})); /** Node.js scripts module. */ var Scripts = /** @class */ (function (_super) { __extends(Scripts, _super); function Scripts() { var _this = _super !== null && _super.apply(this, arguments) || this; /** Absolute path to script files directory. */ _this.envPath = validate_1.isDirectory(_this.environment.get(EScriptsEnv.Path)); /** Observable stream of process exit events. */ _this.processExit$ = new rxjs_1.Subject(); /** Observable stream of process error events. */ _this.processError$ = new rxjs_1.Subject(); /** Workers state. */ _this.scriptsWorkers = {}; return _this; } Scripts.prototype.moduleDependencies = function () { var previous = []; for (var _i = 0; _i < arguments.length; _i++) { previous[_i] = arguments[_i]; } return _super.prototype.moduleDependencies.apply(this, __spreadArrays(previous, [{ process: process_1.Process }])); }; Scripts.prototype.moduleUp = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _super.prototype.moduleUp.apply(this, __spreadArrays(args, [function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { this.log.debug(EScriptsLog.Information, { path: this.envPath }); return [2 /*return*/]; }); }); }])); }; Scripts.prototype.moduleDown = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } // Wait for worker processes to exit. return _super.prototype.moduleDown.apply(this, __spreadArrays(args, [function () { return __awaiter(_this, void 0, void 0, function () { var promises; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: promises = lodash_1.keys(this.scriptsWorkers).map(function (name) { return _this.stopWorker(name).toPromise(); }); return [4 /*yield*/, Promise.all(promises)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }])); }; Scripts.prototype.moduleDestroy = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _super.prototype.moduleDestroy.apply(this, __spreadArrays(args, [function () { _this.processExit$.complete(); _this.processError$.complete(); }])); }; /** Spawn new Node.js process using script file. */ Scripts.prototype.fork = function (fileName, options) { if (options === void 0) { options = {}; } var forkEnv = this.environment.copy(options.env || {}); // Check script file exists and fork. var filePath = validate_1.isFile(path_1.resolve(this.envPath, fileName)); var process = child_process_1.fork(filePath, options.args || [], { env: forkEnv.variables }); return new ScriptsProcess(this, fileName, process); }; Scripts.prototype.startWorker = function (name, fileName, options) { var _this = this; if (options === void 0) { options = {}; } var process = this.fork(fileName, options); if (this.scriptsWorkers[name] == null) { // New worker, create new observables. var next$ = new rxjs_1.BehaviorSubject(process); var unsubscribe$ = new rxjs_1.Subject(); this.scriptsWorkers[name] = { next$: next$, unsubscribe$: unsubscribe$, restarts: 0 }; // Log worker start. var metadata = this.scriptsWorkerLogMetadata({ name: name, worker: this.scriptsWorkers[name], options: options }); this.log.info(EScriptsLog.WorkerStart, metadata); } else { // Restarted worker, update process in state. this.scriptsWorkers[name].unsubscribe$.next(); this.scriptsWorkers[name].next$.next(process); this.scriptsWorkers[name].restarts += 1; } var worker = this.scriptsWorkers[name]; // Handle worker restarts. process.exit$.pipe(operators_1.takeUntil(worker.unsubscribe$)).subscribe(function (code) { // Log worker exit. // TODO(L): Better use of log.notice/error here? var metadata = _this.scriptsWorkerLogMetadata({ name: name, worker: worker, code: code }); _this.log.info(EScriptsLog.WorkerExit, metadata); // Restart worker process by default. if (options.restart == null || !!options.restart) { // Do not restart process if limit reached. if (options.restartLimit == null || worker.restarts < options.restartLimit) { _this.log.info(EScriptsLog.WorkerRestart, metadata); _this.startWorker(name, fileName, options); } else { _this.log.error(EScriptsLog.WorkerRestartLimit, metadata); _this.stopWorker(name); } } }); return worker.next$; }; Scripts.prototype.stopWorker = function (name) { var worker = this.scriptsWorkers[name]; var observable$ = rxjs_1.of(0); if (worker != null) { var process_2 = worker.next$.value; // Observables clean up. worker.unsubscribe$.next(); worker.unsubscribe$.complete(); worker.next$.complete(); // End process if connected. if (process_2.process.connected) { observable$ = process_2.kill(); } // Log worker stop and delete in state. var metadata = this.scriptsWorkerLogMetadata({ name: name, worker: worker }); this.log.info(EScriptsLog.WorkerStop, metadata); delete this.scriptsWorkers[name]; } return observable$; }; Scripts.prototype.scriptsWorkerLogMetadata = function (data) { var metadata = { name: data.name, restarts: data.worker.restarts }; if (data.options != null) { metadata.restart = data.options.restart; metadata.restartLimit = data.options.restartLimit; } if (data.code != null) { metadata.code = data.code; } return metadata; }; /** Default module name. */ Scripts.moduleName = "Scripts"; return Scripts; }(container_1.RxModule)); exports.Scripts = Scripts; //# sourceMappingURL=scripts.js.map