UNPKG

vulcain-corejs

Version:
202 lines (200 loc) 10.8 kB
"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, void 0, 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, void 0, 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