@atomist/automation-client
Version:
Atomist API for software low-level client
212 lines • 8.21 kB
JavaScript
;
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const stringify = require("json-stringify-safe");
const _ = require("lodash");
const AutomationEventListener_1 = require("../../../server/AutomationEventListener");
const MessageClientSupport_1 = require("../../../spi/message/MessageClientSupport");
const logger_1 = require("../../../util/logger");
const memory_1 = require("../../util/memory");
const poll_1 = require("../../util/poll");
const shutdown_1 = require("../../util/shutdown");
const string_1 = require("../../util/string");
const AbstractRequestProcessor_1 = require("../AbstractRequestProcessor");
const RequestProcessor_1 = require("../RequestProcessor");
const messages_1 = require("./messages");
/**
* A RequestProcessor that is being run as Node.JS Cluster worker handling all the actual work.
*/
class ClusterWorkerRequestProcessor extends AbstractRequestProcessor_1.AbstractRequestProcessor {
/* tslint:disable:variable-name */
constructor(_automations, _configuration, _listeners = []) {
super(_automations, _configuration, [..._listeners, new ClusterWorkerAutomationEventListener()]);
this._automations = _automations;
this._configuration = _configuration;
this._listeners = _listeners;
this.shutdownInitiated = false;
// workerSend is async, so we cannot call it in a constructor
process.send({ type: "atomist:online", context: undefined });
process.on("message", msg => {
if (msg.type === "atomist:registration") {
this.setRegistration(msg.registration);
}
else if (msg.type === "atomist:command") {
this.setRegistrationIfRequired(msg);
this.processCommand(decorateContext(msg));
}
else if (msg.type === "atomist:event") {
this.setRegistrationIfRequired(msg);
this.processEvent(decorateContext(msg));
}
else if (msg.type === "atomist:gc") {
memory_1.gc();
}
else if (msg.type === "atomist:heapdump") {
memory_1.heapDump();
}
else if (msg.type === "atomist:mtrace") {
memory_1.mtrace();
}
else if (msg.type === "atomist:shutdown") {
logger_1.logger.debug("Received shutdown message");
this.shutdownInitiated = true;
// async-exit-hook ensures hooks are only run once, so
// this is safe even if worker already received signal
process.kill(process.pid);
}
});
shutdown_1.registerShutdownHook(() => __awaiter(this, void 0, void 0, function* () {
if (this.shutdownInitiated) {
return 0;
}
if (!shutdown_1.terminationGraceful(this._configuration)) {
return 0;
}
const gracePeriod = shutdown_1.terminationGracePeriod(this._configuration);
try {
yield poll_1.poll(() => this.shutdownInitiated, gracePeriod * 2);
}
catch (e) {
logger_1.logger.warn("Did not receive shutdown message from master within twice the grace period");
return 1;
}
return 0;
}), 0, "wait for shutdown message");
}
/* tslint:enable:variable-name */
setRegistration(registration) {
logger_1.logger.debug("Receiving registration '%s'", stringify(registration));
this.registration = registration;
this.graphClients = this._configuration.graphql.client.factory;
}
setRegistrationIfRequired(data) {
if (!this.registration) {
this.setRegistration(data.registration);
}
}
sendShutdown(code, ctx) {
return __awaiter(this, void 0, void 0, function* () {
yield messages_1.workerSend({ type: "atomist:shutdown", data: code, context: ctx.context });
});
}
sendStatusMessage(payload, ctx) {
return messages_1.workerSend({
type: "atomist:status",
context: ctx.context,
data: payload,
});
}
createGraphClient(event, context) {
return this.graphClients.create(RequestProcessor_1.workspaceId(event), this._configuration);
}
createMessageClient(event, context) {
return new ClusterWorkerMessageClient(event, context);
}
setupNamespace(request, automations, invocationId = string_1.guid(), ts = Date.now()) {
const context = request.__context;
delete request.__context;
return context;
}
}
exports.ClusterWorkerRequestProcessor = ClusterWorkerRequestProcessor;
class ClusterWorkerMessageClient extends MessageClientSupport_1.MessageClientSupport {
constructor(event, ctx) {
super();
this.event = event;
this.ctx = ctx;
}
delete(destinations, options) {
return __awaiter(this, void 0, void 0, function* () {
return this.doSend(undefined, Array.isArray(destinations) ? destinations : [destinations], Object.assign(Object.assign({}, options), { delete: true }));
});
}
doSend(msg, destinations, options) {
return messages_1.workerSend({
type: "atomist:message",
context: this.ctx.context,
data: {
message: msg,
destinations,
options,
},
});
}
}
class ClusterWorkerAutomationEventListener extends AutomationEventListener_1.AutomationEventListenerSupport {
commandSuccessful(payload, ctx, result) {
return messages_1.workerSend({
type: "atomist:command_success",
event: payload,
context: ctx.context,
data: sanitize(result),
});
}
commandFailed(payload, ctx, err) {
return messages_1.workerSend({
type: "atomist:command_failure",
event: payload,
context: ctx.context,
data: sanitize(err),
});
}
eventSuccessful(payload, ctx, result) {
return messages_1.workerSend({
type: "atomist:event_success",
event: payload,
context: ctx.context,
data: sanitize(result),
});
}
eventFailed(payload, ctx, err) {
return messages_1.workerSend({
type: "atomist:event_failure",
event: payload,
context: ctx.context,
data: sanitize(err),
});
}
}
/**
* Start a new worker node
* @param {AutomationServer} automations
* @param {WebSocketClientOptions} options
* @returns {RequestProcessor}
*/
function startWorker(automations, configuration, listeners = []) {
const worker = new ClusterWorkerRequestProcessor(automations, configuration, listeners);
return worker;
}
exports.startWorker = startWorker;
function decorateContext(msg) {
const event = msg.data;
event.__context = msg.context;
return event;
}
function sanitize(obj) {
const newObj = {};
_.forEach(obj, (v, k) => {
try {
JSON.stringify(v);
newObj[k] = v;
}
catch (e) {
newObj[k] = "[circular]";
}
});
if (!!obj.message) {
newObj.message = obj.message;
}
if (!!obj.stack) {
newObj.stack = obj.stack;
}
return newObj;
}
//# sourceMappingURL=ClusterWorkerRequestProcessor.js.map