@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
211 lines • 17.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSortedProcessesToStop = exports.EPSProcessManager = exports.ProcessType = void 0;
var child_process_1 = require("child_process");
var error_codes_1 = require("../../../shared/errors/error-codes");
var error_source_1 = require("../../../shared/errors/error-source");
var status_code_1 = require("../../../shared/errors/status-code");
var eps_logger_1 = require("../../logging/eps-logger");
var eps_process_types_1 = require("../../types/eps-process-types");
var exit_code_1 = require("../../types/exit-code");
var ProcessType;
(function (ProcessType) {
/**
* The parent process of eps. The process manager monitors the process.
* If a parent process is killed, the process manager will terminate all
* eps related processes.
*/
ProcessType["parent"] = "parent";
/**
* The sibling process of the main eps process. If a sibling process is killed,
* the process manager will terminate all sibling and child processes.
*/
ProcessType["sibling"] = "sibling";
/**
* The independent child process of eps. e.g. extractor-app. If a child process is
* killed, the process manager will removes it from the watching list. A child process
* will never affect other eps processes.
*/
ProcessType["child"] = "child";
})(ProcessType = exports.ProcessType || (exports.ProcessType = {}));
var EPSProcessManager = /** @class */ (function () {
function EPSProcessManager(tempDataManager) {
this.processes = [];
this.tempDataManager = tempDataManager;
}
EPSProcessManager.prototype.start = function () {
var _this = this;
this.setupMainProcess();
// Checks processes every second
setInterval(function () {
_this.checkProcesses();
}, 1000);
};
EPSProcessManager.prototype.fork = function (epsProcessName, type, modulePath, args, forkOptions) {
if (forkOptions === void 0) { forkOptions = {
detached: false,
silent: false,
execArgv: [],
}; }
try {
var child = (0, child_process_1.fork)(modulePath, args, forkOptions);
child.on('error', function (e) {
// When child process crashes during spawn, error event is fired, which will
// lead to the process exiting in the event cycle. Just log the error here.
e.message = "Error forking process ".concat(epsProcessName, ": ").concat(e);
eps_logger_1.Logger.error(e);
});
if (child.pid !== undefined) {
this.processes.push({
name: epsProcessName,
type: type,
pid: child.pid,
process: child,
});
}
return child;
}
catch (e) {
// based on observation, fork() may throw when the input validations fail
e.message = "Error forking process ".concat(epsProcessName, ": ").concat(e);
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Aborted, error_source_1.ErrorSources.System, error_codes_1.ErrorCodes.M54O33RX, e);
throw e;
}
};
EPSProcessManager.prototype.setupMainProcess = function () {
var _this = this;
process.title = eps_process_types_1.EPSProcessName.EPSMain;
process.on('SIGINT', function () {
_this.stopAllProcesses();
});
// This will probably never occur, log here for debugging purpose, just in case
process.on('error', function (e) {
e.message = "Process '".concat(process.title, "' (pid:").concat(process.pid, ") encountered error with code ").concat(e.code, ": ").concat(e);
eps_logger_1.Logger.error(e);
});
this.processes.push({
name: process.title,
type: ProcessType.sibling,
pid: process.pid,
process: process,
});
};
EPSProcessManager.prototype.checkProcesses = function () {
var _this = this;
this.processes.forEach(function (_a) {
var name = _a.name, pid = _a.pid, type = _a.type;
if (_this.processExists(pid) === false) {
if (type === ProcessType.child) {
// child process might be terminated ungracefully, so remove it from the list
_this.processes = _this.processes.filter(function (p) { return p.pid !== pid; });
return;
}
// stop EPS process when parent or any sibling process has terminated
if (type === ProcessType.sibling) {
eps_logger_1.Logger.error("Process '".concat(name, "' (pid:").concat(pid, ") no longer exists; shutting down EPS."));
}
else if (type === ProcessType.parent) {
eps_logger_1.Logger.info("EPS parent process '".concat(name, "' (pid:").concat(pid, ") has terminated; shutting down EPS."));
}
_this.stopAllProcesses();
}
});
};
EPSProcessManager.prototype.addPID = function (pid, name, type) {
this.processes.push({ name: name, pid: pid, type: type });
eps_logger_1.Logger.info("Added ".concat(type, " process '").concat(name, "' (pid:").concat(pid, ") to be watched; watching ").concat(this.processes.length, " processes."));
return this.processes;
};
EPSProcessManager.prototype.stopChildProcess = function (pid) {
var processInfo = this.processes.find(function (p) { return p.pid === pid; });
if (processInfo === undefined) {
return;
}
var name = processInfo.name, type = processInfo.type;
if (type !== ProcessType.child) {
eps_logger_1.Logger.warn("Only child process can be stopped. '".concat(name, "' (pid: ").concat(pid, ") is a ").concat(type, " process. Skipping..."));
return;
}
try {
// May shift to SIGTERM if needed
process.kill(pid, 'SIGKILL');
this.processes = this.processes.filter(function (p) { return p.pid !== pid; });
}
catch (e) {
eps_logger_1.Logger.error("Error killing process '".concat(processInfo.name, "' (pid:").concat(processInfo.pid, "): ").concat(e));
}
};
EPSProcessManager.prototype.stopAllProcesses = function () {
var epsProcesses = getSortedProcessesToStop(this.processes);
this.tempDataManager.deleteEpsTempDataDir();
eps_logger_1.Logger.info("Killing all EPS processes: ".concat(epsProcesses.map(function (_a) {
var pid = _a.pid;
return pid;
})));
epsProcesses.forEach(function (_a) {
var name = _a.name, pid = _a.pid;
try {
eps_logger_1.Logger.info("Shutting down '".concat(name, "' (pid:").concat(pid, ")."));
process.kill(pid, 'SIGKILL');
}
catch (e) {
if (e.code === 'ESRCH') {
eps_logger_1.Logger.info("Process ".concat(name, " has already exited."));
}
else {
// We don't want to throw an exception because we may have other pids to clean up still
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Internal, error_source_1.ErrorSources.System, error_codes_1.ErrorCodes.EZV90I5V, "Error killing process '".concat(name, "' (pid:").concat(pid, "): ").concat(e));
}
}
});
eps_logger_1.Logger.info("All other processes closed, shutting down main process '".concat(process.title, "' (pid:").concat(process.pid, ")."));
process.exit(exit_code_1.ExitCode.PARENT_PID_TERMINATED);
};
/**
* processExists provides a mechanism to check whether a pid is an active process.
* Advantage of this is that it does not require a separate executable to work.
* The 3PL `processExists` has a dependency on fastlist, which on windows requires
* fastlist.exe. Bundling the fastlist executable with the EPS executable is not
* simple on Windows, so replacing 3PL solution with this fn avoids that dependency.
*/
EPSProcessManager.prototype.processExists = function (pid) {
try {
// Process.kill with 0 code checks process existence, may throw an error as outlined here:
// https://nodejs.org/api/process.html#process_process_kill_pid_signal
// Windows platforms will throw an error if the pid is used to kill a process group.
// An error by process.kill doesn't mean anything is wrong, so we ignore the error and
// compare the code for the returned value.
process.kill(pid, 0);
}
catch (e) {
// This is the expected code path.
return e.code === 'EPERM';
}
// Some processes don't throw error on 0 signal on Windows, so in that case, we know it's true
return true;
};
return EPSProcessManager;
}());
exports.EPSProcessManager = EPSProcessManager;
function getSortedProcessesToStop(processInfos) {
return processInfos
.filter(function (_a) {
var type = _a.type, pid = _a.pid;
// do not kill the parent process and do not self terminate until the end of the function
return type !== ProcessType.parent && pid !== process.pid;
})
.sort(function (first, second) {
// children need to be killed first
if (first.type === ProcessType.child) {
return -1;
}
else if (second.type === ProcessType.child) {
return 1;
}
else {
return 0;
}
});
}
exports.getSortedProcessesToStop = getSortedProcessesToStop;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy1tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2Vwcy9tYWluL2NvbXBvbmVudHMvcHJvY2Vzcy1tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLCtDQUErRDtBQUUvRCxrRUFBK0Q7QUFDL0Qsb0VBQWtFO0FBQ2xFLGtFQUErRDtBQUMvRCx1REFBaUQ7QUFFakQsbUVBQThEO0FBQzlELG1EQUFnRDtBQUVoRCxJQUFZLFdBa0JYO0FBbEJELFdBQVksV0FBVztJQUNyQjs7OztPQUlHO0lBQ0gsZ0NBQWlCLENBQUE7SUFDakI7OztPQUdHO0lBQ0gsa0NBQW1CLENBQUE7SUFDbkI7Ozs7T0FJRztJQUNILDhCQUFlLENBQUE7QUFDakIsQ0FBQyxFQWxCVyxXQUFXLEdBQVgsbUJBQVcsS0FBWCxtQkFBVyxRQWtCdEI7QUFTRDtJQUlFLDJCQUFZLGVBQWdDO1FBSHBDLGNBQVMsR0FBcUIsRUFBRSxDQUFBO1FBSXRDLElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFBO0lBQ3hDLENBQUM7SUFFRCxpQ0FBSyxHQUFMO1FBQUEsaUJBTUM7UUFMQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQTtRQUN2QixnQ0FBZ0M7UUFDaEMsV0FBVyxDQUFDO1lBQ1YsS0FBSSxDQUFDLGNBQWMsRUFBRSxDQUFBO1FBQ3ZCLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQTtJQUNWLENBQUM7SUFFRCxnQ0FBSSxHQUFKLFVBQ0UsY0FBOEIsRUFDOUIsSUFBNkMsRUFDN0MsVUFBa0IsRUFDbEIsSUFBYyxFQUNkLFdBSUM7UUFKRCw0QkFBQSxFQUFBO1lBQ0UsUUFBUSxFQUFFLEtBQUs7WUFDZixNQUFNLEVBQUUsS0FBSztZQUNiLFFBQVEsRUFBRSxFQUFFO1NBQ2I7UUFFRCxJQUFJO1lBQ0YsSUFBTSxLQUFLLEdBQUcsSUFBQSxvQkFBSSxFQUFDLFVBQVUsRUFBRSxJQUFJLEVBQUUsV0FBVyxDQUFD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