opossum
repository·main·Indexed 23 days ago
https://github.com/nodeshift/opossumA Node.js implementation of the circuit breaker pattern designed to wrap asynchronous functions, promises, and callbacks. It monitors success/failure rates to automatically stop execution (fail fast) when error thresholds are reached, preventing cascading failures and maintaining system stability. Features include fallback functions, AbortController support for request cancellation, state persistence for ephemeral environments, and rolling statistical windows for monitoring performance.
What's inside opossum
- Opossum is a Node.js circuit breaker designed to execute asynchronous functions while monitoring their execution status. It implements the circuit breaker pattern to prevent cascading failures: when a threshold of failures is reached, the breaker 'opens' (plays dead) and fails fast to protect your system. You can optionally provide a fallback function to execute when the breaker is in a failure state.
Implement a Fallback function
mainA fallback function can be registered using
.fallback(). This function executes when the protected action fails or when the circuit is open. The fallback function receives the same parameters as the original function. You can listen for thefallbackevent to perform side effects when a fallback is triggered.const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options); // if asyncFunctionThatCouldFail starts to fail, firing the breaker // will trigger our fallback function breaker.fallback(() => 'Sorry, out of service right now'); breaker.on('fallback', (result) => reportFallbackEvent(result)); // Example with parameters: const delay = (delay, a, b, c) => new Promise((resolve) => { setTimeout(() => { resolve(); }, delay); }); const breakerWithParams = new CircuitBreaker(delay); breakerWithParams.fire(20000, 1, 2, 3); breakerWithParams.fallback((delay, a, b, c) => `Sorry, out of service right now. But your parameters are: ${delay}, ${a}, ${b} and ${c}`);Initialize Breaker state and status in ephemeral environments
mainIn serverless or container-based environments (like AWS Lambda or Knative), you can persist and restore the state and statistics of a
CircuitBreakerto handle ephemeral lifecycles.To export state: Use
breaker.toJSON()to get the current state and status. To import state: Pass the exported state object to the constructor via{state: state}.To export stats: Access
breaker.statsto get cumulative statistics. To import stats: Create a newStatusobject usingCircuitBreaker.newStatus({ stats: ... })and pass it to the constructor via{status: newStatus}.Use Promises or Callbacks with CircuitBreaker
mainWhile
CircuitBreaker.fire()always returns a Promise, the protected function itself can be a standard Node.js callback-style function. Useutil.promisify()to convert these functions before passing them to theCircuitBreakerconstructor.const fs = require('fs'); const { promisify } = require('util'); const CircuitBreaker = require('opossum'); const readFile = promisify(fs.readFile); const breaker = new CircuitBreaker(readFile, options); breaker.fire('./package.json', 'utf-8') .then(console.log) .catch(console.error);Auto Renew AbortController
mainSetting
autoRenewAbortController: truein the options allows Opossum to automatically renew theAbortControllerwhen the circuit transitions intohalfOpenorclosedstates. This enables reusing the controller for ongoing requests without manual intervention. Usebreaker.getSignal()to retrieve the current signal.const CircuitBreaker = require('opossum'); const http = require('http'); function asyncFunctionThatCouldFail(abortSignal, x, y) { return new Promise((resolve, reject) => { http.get( 'http://httpbin.org/delay/10', { signal: abortSignal }, (res) => { if(res.statusCode < 300) { resolve(res.statusCode); return; } reject(res.statusCode); } ); }); } const abortController = new AbortController(); const options = { autoRenewAbortController: true, timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure }; const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options); const signal = breaker.getSignal(); breaker.fire(signal) .then(console.log) .catch(console.error);Basic Usage of CircuitBreaker
mainWrap functions that depend on potentially failing operations (like network calls or disk I/O) in a
CircuitBreakerto gain control over failure states. You can pass an options object to configure timeouts, error thresholds, and reset intervals. Use.fire()to execute the protected function, which returns a Promise.const CircuitBreaker = require('opossum'); function asyncFunctionThatCouldFail(x, y) { return new Promise((resolve, reject) => { // Do something, maybe on the network or a disk }); } const options = { timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure errorThresholdPercentage: 50, // When 50% of requests fail, trip the circuit resetTimeout: 30000 // After 30 seconds, try again. }; const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options); breaker.fire(x, y) .then(console.log) .catch(console.error);Use CircuitBreaker in the Browser
mainOpossum can be used in the browser to guard against network failures in AJAX/REST calls. To use it globally, include
opossum.jsin your HTML. If using a bundler like Webpack, it won't pollute the global namespace. If serving via a server (e.g., Hapi.js), point a route tonode_modules/opossum/dist/opossum-min.js.<head> <script type='text/javascript' src='/opossum.js'></script> </head>// app.js const route = 'https://example-service.com/rest/route'; const circuitBreakerOptions = { timeout: 500, errorThresholdPercentage: 50, resetTimeout: 5000 }; const breaker = new CircuitBreaker(() => $.get(route), circuitBreakerOptions); breaker.fallback(() => `${route} unavailable right now. Try later.`)); breaker.on('success', (result) => $(element).append(JSON.stringify(result))); $(() => { $('#serviceButton').click(() => breaker.fire().catch((e) => console.error(e))); });AbortController support for request cancellation
mainYou can provide an
AbortControllerin the options to allow Opossum to abort ongoing requests when a timeout is reached. TheAbortSignalmust be passed as an argument to the protected function via.fire().const CircuitBreaker = require('opossum'); const http = require('http'); function asyncFunctionThatCouldFail(abortSignal, x, y) { return new Promise((resolve, reject) => { http.get( 'http://httpbin.org/delay/10', { signal: abortSignal }, (res) => { if(res.statusCode < 300) { resolve(res.statusCode); return; } reject(res.statusCode); } ); }); } const abortController = new AbortController(); const options = { abortController, timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure }; const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options); breaker.fire(abortController.signal) .then(console.log) .catch(console.error);Configure Coalescing calls
mainWhen
options.coalesceis enabled, multiple calls to the circuit breaker within theoptions.coalesceTTLtimeframe are handled as a single call. This improves performance for rapidly repeating requests.To control when coalescing resets (e.g., to ensure errors or timeouts aren't swallowed by the coalescing logic), use the
coalesceResetOnoption. Valid values include:error,success,timeout: Resets after every 'done' status (only concurrent 'running' calls are coalesced).error,timeout: Resets on errors and timeouts.error: Resets on errors.timeout: Resets on timeouts.success: Resets on success.
Resolve MaxListenersExceededWarning in Opossum
mainIf you encounter
MaxListenersExceededWarningrelated toEventEmittermemory leaks (e.g.,unpipe,drain,error,close, orfinishlisteners), it may be due to having manyCircuitBreakerinstances or a large test suite creating them repeatedly.To resolve this, you have two options depending on your use case:
- Increase the listener limit: If you legitimately need many listeners on the statistics stream, increase the limit on the Hystrix stream.
- Clean up after tests: If you are creating
CircuitBreakerinstances for short-lived tasks (like in a test suite), callbreaker.shutdown()when the breaker is no longer needed to clean up all listeners and prevent leaks.
Configure Status window and percentiles
mainWhen initializing a
CircuitBreaker, you can configure how theStatusinstance behaves using the following options:Option Type Default Description rollingCountBucketsNumber10The number of time-sliced buckets in the rolling window. rollingCountTimeoutNumber10000The total duration of the rolling window in milliseconds. rollingPercentilesEnabledBooleantrueWhether to calculate latency percentiles and mean. enableSnapshotsBooleantrueWhether to emit the 'snapshot'event periodically.statsObjectundefinedAn object of previous stats to prime the window. rotateBucketControllerEventEmitterundefinedAn optional EventEmitter to trigger bucket rotation manually instead of using a timer. Listen to CircuitBreaker events
mainThe
CircuitBreakeremits several events that allow you to monitor and react to its lifecycle and failures. Common events include:fire: Emitted when the breaker is fired.reject: Emitted when the breaker is open or halfOpen.timeout: Emitted when the action times out.success: Emitted when the action completes successfully.failure: Emitted when the action fails (includes the error).open: Emitted when the state changes toopen.close: Emitted when the state changes toclosed.halfOpen: Emitted when the state changes tohalfOpen.fallback: Emitted when a fallback is executed.semaphoreLocked: Emitted when the breaker is at capacity.shutdown: Emitted when the breaker shuts down.