UNPKG

node-circuit-breaker

Version:

Circuit Breaker: Decorators and tools that can easily apply the Circuit Breaker pattern.

168 lines (167 loc) 7.02 kB
"use strict"; 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 worker_threads_1 = require("worker_threads"); var CircuitBreakerState; (function (CircuitBreakerState) { CircuitBreakerState[CircuitBreakerState["Closed"] = 0] = "Closed"; CircuitBreakerState[CircuitBreakerState["Open"] = 1] = "Open"; CircuitBreakerState[CircuitBreakerState["HalfOpen"] = 2] = "HalfOpen"; })(CircuitBreakerState || (CircuitBreakerState = {})); const channel = new worker_threads_1.BroadcastChannel('worker'); if (worker_threads_1.isMainThread) { let worker; let terminated = false; let metadataProvider; let stateCallback; function createWorker() { if (terminated) return; worker = new worker_threads_1.Worker(__filename, { resourceLimits: {} }); worker.on('error', (e) => __awaiter(this, void 0, void 0, function* () { console.error(e); yield worker.terminate(); })); worker.on('exit', (code) => { if (!terminated && code !== 0) console.error(`Worker stopped with exit code ${code}`); createWorker(); }); } createWorker(); channel.onmessage = ({ data: { mode, id, data } }) => __awaiter(void 0, void 0, void 0, function* () { // console.log('parent got message', mode, id, data); switch (mode) { case 'metadata': channel.postMessage({ mode, id, data: metadataProvider(id) }); break; case 'circuit-open': { if (!(yield stateCallback(id, CircuitBreakerState.Open))) channel.postMessage({ mode: 'circuit-close', id }); break; } case 'circuit-halfopen': { if (!(yield stateCallback(id, CircuitBreakerState.HalfOpen))) channel.postMessage({ mode: 'circuit-open', id }); break; } case 'circuit-close': { if (!(yield stateCallback(id, CircuitBreakerState.Closed))) channel.postMessage({ mode: 'circuit-open', id }); break; } } }); channel.onmessageerror = (ev) => { console.error(ev); }; module.exports = { setMetadataProvider: (provider) => { metadataProvider = provider; }, setStateCallback: (callback) => { stateCallback = callback; }, setState: (id, state) => { channel.postMessage({ mode: state === CircuitBreakerState.Open ? 'circuit-open' : 'circuit-close', id }); }, pushError: (id, errorType) => { // console.log(id, errorType); channel.postMessage({ mode: 'push-error', id, data: { errorType } }); }, terminate: () => __awaiter(void 0, void 0, void 0, function* () { terminated = true; yield (worker === null || worker === void 0 ? void 0 : worker.terminate()); channel.close(); }), }; } else { const metadata = {}; const metadataRequested = {}; const errorRecords = {}; const openStates = {}; const debounce = {}; function openProcess(id) { var _a; delete debounce[id]; if (((_a = openStates[id]) === null || _a === void 0 ? void 0 : _a.state) === CircuitBreakerState.Open || !metadata[id]) { // console.log('already open'); return; } const meta = metadata[id]; errorRecords[id] = errorRecords[id] || []; const oldestTime = Date.now() - meta.rules.reduce((result, item) => Math.max(item.inSeconds), 0) * 1000; while (errorRecords[id].length > 0 && errorRecords[id][0].time < oldestTime) errorRecords[id].shift(); const records = errorRecords[id] || []; if (meta.rules.some((rule) => { const time = Date.now() - Math.max(rule.inSeconds) * 1000; const filtered = records.filter((record) => rule.exceptions.includes(record.error) && record.time >= time); return rule.times <= filtered.length; })) { openStates[id] = { state: CircuitBreakerState.Open, changed: Date.now() }; channel.postMessage({ mode: 'circuit-open', id }); } } setInterval(() => { if (Object.keys(openStates).length <= 0) return; // console.log(openStates); const now = Date.now(); const ids = Object.keys(openStates); for (const id of ids) { if (openStates[id].state !== CircuitBreakerState.Open) continue; const meta = metadata[id]; if (meta.fallbackForSeconds * 1000 + openStates[id].changed > now) { openStates[id].state = CircuitBreakerState.HalfOpen; channel.postMessage({ mode: 'circuit-halfopen', id }); } } }, 1000); channel.onmessageerror = (ev) => { console.error(ev); }; channel.onmessage = ({ data: { mode, id, data } }) => __awaiter(void 0, void 0, void 0, function* () { // console.log('child got message', mode, id, data); switch (mode) { case 'push-error': if (!errorRecords[id]) errorRecords[id] = []; errorRecords[id].push({ time: Date.now(), error: data.errorType }); if (metadata[id]) { if (debounce[id]) clearImmediate(debounce[id]); debounce[id] = setImmediate(() => openProcess(id)); } else { if (!metadataRequested[id]) { channel.postMessage({ mode: 'metadata', id }); metadataRequested[id] = true; } return; } break; case 'circuit-open': openStates[id] = { state: CircuitBreakerState.Open, changed: Date.now() }; break; case 'circuit-close': delete openStates[id]; errorRecords[id] = []; break; case 'metadata': metadata[id] = data; openProcess(id); break; } }); }