UNPKG

ruchy-syntax-tools

Version:

Comprehensive syntax highlighting and language support for the Ruchy programming language

250 lines 9.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CircuitBreakerPolicy = exports.CircuitState = void 0; const Backoff_1 = require("./backoff/Backoff"); const abort_1 = require("./common/abort"); const Event_1 = require("./common/Event"); const Executor_1 = require("./common/Executor"); const Errors_1 = require("./errors/Errors"); const IsolatedCircuitError_1 = require("./errors/IsolatedCircuitError"); var CircuitState; (function (CircuitState) { /** * Normal operation. Execution of actions allowed. */ CircuitState[CircuitState["Closed"] = 0] = "Closed"; /** * The automated controller has opened the circuit. Execution of actions blocked. */ CircuitState[CircuitState["Open"] = 1] = "Open"; /** * Recovering from open state, after the automated break duration has * expired. Execution of actions permitted. Success of subsequent action/s * controls onward transition to Open or Closed state. */ CircuitState[CircuitState["HalfOpen"] = 2] = "HalfOpen"; /** * Circuit held manually in an open state. Execution of actions blocked. */ CircuitState[CircuitState["Isolated"] = 3] = "Isolated"; })(CircuitState || (exports.CircuitState = CircuitState = {})); class CircuitBreakerPolicy { /** * Gets the current circuit breaker state. */ get state() { return this.innerState.value; } /** * Gets the last reason the circuit breaker failed. */ get lastFailure() { return this.innerLastFailure; } constructor(options, executor) { this.options = options; this.executor = executor; this.breakEmitter = new Event_1.EventEmitter(); this.resetEmitter = new Event_1.EventEmitter(); this.halfOpenEmitter = new Event_1.EventEmitter(); this.stateChangeEmitter = new Event_1.EventEmitter(); this.innerState = { value: CircuitState.Closed }; /** * Event emitted when the circuit breaker opens. */ this.onBreak = this.breakEmitter.addListener; /** * Event emitted when the circuit breaker resets. */ this.onReset = this.resetEmitter.addListener; /** * Event emitted when the circuit breaker is half open (running a test call). * Either `onBreak` on `onReset` will subsequently fire. */ this.onHalfOpen = this.halfOpenEmitter.addListener; /** * Fired whenever the circuit breaker state changes. */ this.onStateChange = this.stateChangeEmitter.addListener; /** * @inheritdoc */ this.onSuccess = this.executor.onSuccess; /** * @inheritdoc */ this.onFailure = this.executor.onFailure; this.halfOpenAfterBackoffFactory = typeof options.halfOpenAfter === 'number' ? new Backoff_1.ConstantBackoff(options.halfOpenAfter) : options.halfOpenAfter; if (options.initialState) { const initialState = options.initialState; this.innerState = initialState.ownState; this.options.breaker.state = initialState.breakerState; if (this.innerState.value === CircuitState.Open || this.innerState.value === CircuitState.HalfOpen) { this.innerLastFailure = { error: new Errors_1.HydratingCircuitError() }; let backoff = this.halfOpenAfterBackoffFactory.next({ attempt: 1, result: this.innerLastFailure, signal: abort_1.neverAbortedSignal, }); for (let i = 2; i <= this.innerState.attemptNo; i++) { backoff = backoff.next({ attempt: i, result: this.innerLastFailure, signal: abort_1.neverAbortedSignal, }); } this.innerState.backoff = backoff; } } } /** * Manually holds open the circuit breaker. * @returns A handle that keeps the breaker open until `.dispose()` is called. */ isolate() { if (this.innerState.value !== CircuitState.Isolated) { this.innerState = { value: CircuitState.Isolated, counters: 0 }; this.breakEmitter.emit({ isolated: true }); this.stateChangeEmitter.emit(CircuitState.Isolated); } this.innerState.counters++; let disposed = false; return { dispose: () => { if (disposed) { return; } disposed = true; if (this.innerState.value === CircuitState.Isolated && !--this.innerState.counters) { this.innerState = { value: CircuitState.Closed }; this.resetEmitter.emit(); this.stateChangeEmitter.emit(CircuitState.Closed); } }, }; } /** * Executes the given function. * @param fn Function to run * @throws a {@link BrokenCircuitError} if the circuit is open * @throws a {@link IsolatedCircuitError} if the circuit is held * open via {@link CircuitBreakerPolicy.isolate} * @returns a Promise that resolves or rejects with the function results. */ async execute(fn, signal = abort_1.neverAbortedSignal) { const state = this.innerState; switch (state.value) { case CircuitState.Closed: const result = await this.executor.invoke(fn, { signal }); if ('success' in result) { this.options.breaker.success(state.value); } else { this.innerLastFailure = result; if (this.options.breaker.failure(state.value)) { this.open(result, signal); } } return (0, Executor_1.returnOrThrow)(result); case CircuitState.HalfOpen: await state.test.catch(() => undefined); if (this.state === CircuitState.Closed && signal.aborted) { throw new Errors_1.TaskCancelledError(); } return this.execute(fn); case CircuitState.Open: if (Date.now() - state.openedAt < state.backoff.duration) { throw new Errors_1.BrokenCircuitError(); } const test = this.halfOpen(fn, signal); this.innerState = { value: CircuitState.HalfOpen, test, backoff: state.backoff, attemptNo: state.attemptNo + 1, }; this.stateChangeEmitter.emit(CircuitState.HalfOpen); return test; case CircuitState.Isolated: throw new IsolatedCircuitError_1.IsolatedCircuitError(); default: throw new Error(`Unexpected circuit state ${state}`); } } /** * Captures circuit breaker state that can later be used to recreate the * breaker by passing `state` to the `circuitBreaker` function. This is * useful in cases like serverless functions where you may want to keep * the breaker state across multiple executions. */ toJSON() { const state = this.innerState; let ownState; if (state.value === CircuitState.HalfOpen) { ownState = { value: CircuitState.Open, openedAt: 0, attemptNo: state.attemptNo, }; } else if (state.value === CircuitState.Open) { ownState = { value: CircuitState.Open, openedAt: state.openedAt, attemptNo: state.attemptNo, }; } else { ownState = state; } return { ownState, breakerState: this.options.breaker.state }; } async halfOpen(fn, signal) { this.halfOpenEmitter.emit(); try { const result = await this.executor.invoke(fn, { signal }); if ('success' in result) { this.options.breaker.success(CircuitState.HalfOpen); this.close(); } else { this.innerLastFailure = result; this.options.breaker.failure(CircuitState.HalfOpen); this.open(result, signal); } return (0, Executor_1.returnOrThrow)(result); } catch (err) { // It's an error, but not one the circuit is meant to retry, so // for our purposes it's a success. Task failed successfully! this.close(); throw err; } } open(reason, signal) { if (this.state === CircuitState.Isolated || this.state === CircuitState.Open) { return; } const attemptNo = this.innerState.value === CircuitState.HalfOpen ? this.innerState.attemptNo : 1; const context = { attempt: attemptNo, result: reason, signal }; const backoff = this.innerState.value === CircuitState.HalfOpen ? this.innerState.backoff.next(context) : this.halfOpenAfterBackoffFactory.next(context); this.innerState = { value: CircuitState.Open, openedAt: Date.now(), backoff, attemptNo }; this.breakEmitter.emit(reason); this.stateChangeEmitter.emit(CircuitState.Open); } close() { if (this.state === CircuitState.HalfOpen) { this.innerState = { value: CircuitState.Closed }; this.resetEmitter.emit(); this.stateChangeEmitter.emit(CircuitState.Closed); } } } exports.CircuitBreakerPolicy = CircuitBreakerPolicy; //# sourceMappingURL=CircuitBreakerPolicy.js.map