nodeshift / opossum

Node.js circuit breaker - fails fast ⚡️
https://nodeshift.dev/opossum/
Apache License 2.0
1.3k stars 107 forks source link

Create the opossum circuit breaker in node js as a Singleton instance #842

Closed PavithranRick closed 7 months ago

PavithranRick commented 10 months ago

Am trying to implement a circuit breaker in node js as a singleton class using https://nodeshift.dev/opossum/ For some reason, breaker metrics always start from 0 and the circuit never opens after the threshold failure.

REF: https://github.com/nodeshift/opossum/issues/593 -> The author seems to suggest using closure to achieve singleton property but doesn't work for me. Any reason why or pointers I can check?

Node.js Version: v18.15.0

Operating System: Darwin C02G540YMD6R 22.2.0 Darwin Kernel Version 22.2.0: Fri Nov 11 02:08:47 PST 2022; root:xnu-8792.61.2~4/RELEASE_X86_64 x86_64

Steps to Produce Error: https://filebin.net/zbjhntvjnqwdobns (download and use the command (npm install && node src/singleton.js) to run it locally)

singleton.js

let Adapter = require('./adapter');
let routerHandlerWrapper = require('./handler-wrapper');

const start = async function () {
  let i=0;
  for (i = 0; i < 100; i++) {
    const adapter = new Adapter(routerHandlerWrapper);
    await adapter.callPartner(i);
    console.log("attempt: " + i);
  }
}
start();

handler-wrapper.js

const { breaker } = require('./circuit-breaker');
module.exports = function (routerController, next) {
  console.log("Creating controller circuit");
  const breakerController = breaker(routerController);
  breakerController.on('open', () => {
    console.log('The circuit is open ' + JSON.stringify(breakerController.stats));
  });

  return async function (req, res, i) {
    try {
      // console.log(breakerController.stats);
      await breakerController.fire(req, res, i);
    } catch (err) {
      next(err);
    }
  };
};

circuit-breaker.js

const CircuitBreaker = require('opossum');

const circuitBreakerOptions = {
  enabled: true,
  timeout: 10000, // If our function takes longer than 10 seconds, trigger a failure
  errorThresholdPercentage: 50, // When 50% of requests fail, trip the circuit
  volumeThreshold: 5,
  rollingCountTimeout: 300000,
  resetTimeout: 20000 // After 60 seconds, try again.
}

const options = {
  // errorFilter: err => ((!err || !err.code)
  //   ? false // open circuit breaker
  //   : (err.code >= 300 && err.code < 500))
  ...circuitBreakerOptions
};

module.exports = {
  breaker: asyncFunc => new CircuitBreaker(asyncFunc, options)
};

adapter.js

class Adapter {
  constructor(routerHandlerWrapper) {
    this.routerHandlerWrapper = routerHandlerWrapper
  }
  async asyncFunctionThatCouldFail(x, y, i) {
    if (i > 40) {
      return await new Promise((resolve, reject) => setTimeout(() => resolve("positive"), 1000));
    }
    await new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeout')), 1000));
    console.log("the args: " + x + " " + y + " " + i);
    throw "error";
  }

  async callPartner(i) {
    const x = "value1", y = "value2";
    let wrappedFunc = this.routerHandlerWrapper(this.asyncFunctionThatCouldFail, console.error);
    await wrappedFunc(x, y, i);
  }
}

module.exports = Adapter;
yukha-dw commented 10 months ago

I think because you create new Adapter on every increment? Singleton is summoned by calling a method who determine to create new object or not

github-actions[bot] commented 9 months ago

This issue is stale because it has been open 30 days with no activity.

lholmquist commented 7 months ago

i think this is explained here: https://github.com/nodeshift/opossum/issues/593