opossum

repository·main·Indexed 23 days ago

https://github.com/nodeshift/opossum

A 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.

Tokens
3.6K
Snippets
6
Records
15
Agent score
82%

What's inside opossum

  1. What is opossum?

    main
    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.
  2. Implement a Fallback function

    main

    A 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 the fallback event 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}`);
  3. Initialize Breaker state and status in ephemeral environments

    main

    In serverless or container-based environments (like AWS Lambda or Knative), you can persist and restore the state and statistics of a CircuitBreaker to 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.stats to get cumulative statistics. To import stats: Create a new Status object using CircuitBreaker.newStatus({ stats: ... }) and pass it to the constructor via {status: newStatus}.

  4. Use Promises or Callbacks with CircuitBreaker

    main

    While CircuitBreaker.fire() always returns a Promise, the protected function itself can be a standard Node.js callback-style function. Use util.promisify() to convert these functions before passing them to the CircuitBreaker constructor.

    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);
  5. Auto Renew AbortController

    main

    Setting autoRenewAbortController: true in the options allows Opossum to automatically renew the AbortController when the circuit transitions into halfOpen or closed states. This enables reusing the controller for ongoing requests without manual intervention. Use breaker.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);
  6. Basic Usage of CircuitBreaker

    main

    Wrap functions that depend on potentially failing operations (like network calls or disk I/O) in a CircuitBreaker to 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);
  7. Use CircuitBreaker in the Browser

    main

    Opossum can be used in the browser to guard against network failures in AJAX/REST calls. To use it globally, include opossum.js in 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 to node_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)));
    });
  8. AbortController support for request cancellation

    main

    You can provide an AbortController in the options to allow Opossum to abort ongoing requests when a timeout is reached. The AbortSignal must 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);
  9. Configure Coalescing calls

    main

    When options.coalesce is enabled, multiple calls to the circuit breaker within the options.coalesceTTL timeframe 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 coalesceResetOn option. 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.
  10. Resolve MaxListenersExceededWarning in Opossum

    main

    If you encounter MaxListenersExceededWarning related to EventEmitter memory leaks (e.g., unpipe, drain, error, close, or finish listeners), it may be due to having many CircuitBreaker instances or a large test suite creating them repeatedly.

    To resolve this, you have two options depending on your use case:

    1. Increase the listener limit: If you legitimately need many listeners on the statistics stream, increase the limit on the Hystrix stream.
    2. Clean up after tests: If you are creating CircuitBreaker instances for short-lived tasks (like in a test suite), call breaker.shutdown() when the breaker is no longer needed to clean up all listeners and prevent leaks.
  11. Configure Status window and percentiles

    main

    When initializing a CircuitBreaker, you can configure how the Status instance behaves using the following options:

    OptionTypeDefaultDescription
    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.
  12. Listen to CircuitBreaker events

    main

    The CircuitBreaker emits 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 to open.
    • close: Emitted when the state changes to closed.
    • halfOpen: Emitted when the state changes to halfOpen.
    • fallback: Emitted when a fallback is executed.
    • semaphoreLocked: Emitted when the breaker is at capacity.
    • shutdown: Emitted when the breaker shuts down.