vulcain-corejs
Version:
Vulcain micro-service framework
202 lines (200 loc) • 10.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
const commandMetricsFactory_1 = require("../metrics/commandMetricsFactory");
const circuitBreaker_1 = require("./circuitBreaker");
const actualTime_1 = require("../../utils/actualTime");
const semaphore_1 = require('./semaphore');
const executionResult_1 = require('./executionResult');
const timeoutError_1 = require('./../../errors/timeoutError');
const commandRuntimeError_1 = require('./../../errors/commandRuntimeError');
const badRequestError_1 = require('./../../errors/badRequestError');
const system_1 = require('./../../configurations/globals/system');
class HystrixCommand {
constructor(properties, command, context) {
this.properties = properties;
this.command = command;
this.context = context;
this.status = new executionResult_1.ExecutionResult();
command.requestContext = context;
this.metrics = commandMetricsFactory_1.CommandMetricsFactory.getOrCreate(properties);
}
get circuitBreaker() {
return circuitBreaker_1.CircuitBreakerFactory.getOrCreate(this.properties);
}
get semaphore() {
return semaphore_1.SemaphoreFactory.getOrCreate(this.properties);
}
setSchemaOnCommandAsync(schema) {
return __awaiter(this, void 0, void 0, function* () {
if (schema && this.command.setSchemaAsync)
yield this.command.setSchemaAsync(schema);
});
}
executeAsync(...args) {
return __awaiter(this, arguments, Promise, function* () {
if (this.running) {
throw new Error("This instance can only be executed once. Please instantiate a new instance.");
}
this.running = true;
this._arguments = arguments;
let result;
// Get form Cache
// Execution
this.metrics.incrementExecutionCount();
let start = actualTime_1.default.getCurrentTime();
try {
if (this.circuitBreaker.allowRequest()) {
if (this.semaphore.canExecuteCommand()) {
try {
try {
// Execution
let executing = true;
let promises = [];
promises.push(this.command.runAsync.apply(this.command, this._arguments));
if (this.properties.executionTimeoutInMilliseconds.value > 0) {
promises.push(new Promise((resolve, reject) => setTimeout(() => { if (executing)
reject(new timeoutError_1.TimeoutError(this.properties.executionTimeoutInMilliseconds.value)); }, this.properties.executionTimeoutInMilliseconds.value)));
}
result = yield Promise.race(promises);
executing = false; // avoid timeout rejection
}
catch (e) {
let end = actualTime_1.default.getCurrentTime();
// timeout
if (e instanceof timeoutError_1.TimeoutError) {
this.metrics.markTimeout();
return yield this.getFallbackOrThrowException(executionResult_1.EventType.TIMEOUT, executionResult_1.FailureType.TIMEOUT, "timed-out", e);
}
else {
return yield this.onExecutionError(end - start, e);
}
}
// Execution complete correctly
let duration = actualTime_1.default.getCurrentTime() - start;
this.metrics.markSuccess();
this.metrics.addExecutionTime(duration);
this.circuitBreaker.markSuccess();
this.status.addEvent(executionResult_1.EventType.SUCCESS);
this.command.onCommandCompleted && this.command.onCommandCompleted(duration, true);
// Update cache
// TODO
start = -1;
return result;
}
finally {
this.semaphore.releaseExecutionCommand();
}
}
else {
this.metrics.markRejected();
return yield this.getFallbackOrThrowException(executionResult_1.EventType.SEMAPHORE_REJECTED, executionResult_1.FailureType.REJECTED_SEMAPHORE_EXECUTION, "could not acquire a semaphore for execution", new Error("could not acquire a semaphore for execution"));
}
}
else {
start = -1;
this.metrics.markShortCircuited();
return yield this.getFallbackOrThrowException(executionResult_1.EventType.SHORT_CIRCUITED, executionResult_1.FailureType.SHORTCIRCUIT, "short-circuited", new Error(" circuit short-circuited and is OPEN"));
}
}
finally {
if (start >= 0) {
let duration = actualTime_1.default.getCurrentTime() - start;
this.command.onCommandCompleted && this.command.onCommandCompleted(duration, false);
this.recordTotalExecutionTime(duration);
}
this.metrics.decrementExecutionCount();
this.status.isExecutionComplete = true;
}
});
}
getFallbackOrThrowException(eventType, failureType, message, error) {
return __awaiter(this, void 0, Promise, function* () {
this.logInfo(error.message || error.toString());
try {
if (this.isUnrecoverable(error)) {
this.logInfo("Unrecoverable error for command so will throw CommandRuntimeError and not apply fallback " + error);
this.status.addEvent(eventType);
throw new commandRuntimeError_1.CommandRuntimeError(failureType, this.properties.commandName, this.getLogMessagePrefix() + " " + message + " and encountered unrecoverable error", error);
}
let fallback = this.command.fallbackAsync;
if (!fallback) {
//this.logInfo("No fallback for command");
throw new commandRuntimeError_1.CommandRuntimeError(failureType, this.properties.commandName, this.getLogMessagePrefix() + " " + message + " and no fallback provided.", error);
}
if (this.semaphore.canExecuteFallback()) {
try {
this.logInfo("Use fallback for command");
let result = yield fallback.apply(this.command, this._arguments);
this.metrics.markFallbackSuccess();
this.status.addEvent(executionResult_1.EventType.FALLBACK_SUCCESS);
return result;
}
catch (e) {
this.logInfo("Fallback failed " + e);
this.metrics.markFallbackFailure();
this.status.addEvent(executionResult_1.EventType.FALLBACK_FAILURE);
throw new commandRuntimeError_1.CommandRuntimeError(failureType, this.properties.commandName, this.getLogMessagePrefix() + " and fallback failed.", e);
}
finally {
this.semaphore.releaseFallback();
}
}
else {
this.logInfo("Command fallback rejection.");
this.metrics.markFallbackRejection();
this.status.addEvent(executionResult_1.EventType.FALLBACK_REJECTION);
throw new commandRuntimeError_1.CommandRuntimeError(executionResult_1.FailureType.REJECTED_SEMAPHORE_FALLBACK, this.properties.commandName, this.getLogMessagePrefix() + " fallback execution rejected.");
}
}
catch (e) {
this.metrics.markExceptionThrown();
throw e;
}
});
}
onExecutionError(ms, e) {
e = e || new Error("Unknow error");
if (e instanceof badRequestError_1.BadRequestError) {
this.metrics.markBadRequest(ms);
throw e;
}
this.logInfo(`Error executing command ${e.stack} Proceeding to fallback logic...`);
this.metrics.markFailure();
this.status.failedExecutionException = e;
return this.getFallbackOrThrowException(executionResult_1.EventType.FAILURE, executionResult_1.FailureType.COMMAND_EXCEPTION, "failed", e);
}
logInfo(msg) {
system_1.System.log.info(this.context, this.properties.commandName + ": " + msg);
}
isUnrecoverable(e) {
return false;
}
getLogMessagePrefix() {
return this.properties.commandName;
}
///
/// Record the duration of execution as response or exception is being returned to the caller.
///
recordTotalExecutionTime(duration) {
// the total execution time for the user thread including queuing, thread scheduling, run() execution
this.metrics.addExecutionTime(duration);
/*
* We record the executionTime for command execution.
*
* If the command is never executed (rejected, short-circuited, etc) then it will be left unset.
*
* For this metric we include failures and successes as we use it for per-request profiling and debugging
* whereas 'metrics.addCommandExecutionTime(duration)' is used by stats across many requests.
*/
this.status.executionTime = duration;
}
}
exports.HystrixCommand = HystrixCommand;
//# sourceMappingURL=command.js.map