Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circuit Opened on first attempt and executing action after threshold is reached #288

Closed
sesteva opened this issue Apr 17, 2019 · 3 comments

Comments

@sesteva
Copy link

sesteva commented Apr 17, 2019

Thank you for creating this module. It is just what I was looking for.

Node.js Version:
v11.6.0
Operating System:
Darwin C02WG1E2HTDD 17.5.0 Darwin Kernel Version 17.5.0: Fri Apr 13 19:32:32 PDT 2018; root:xnu-4570.51.2~1/RELEASE_X86_64 x86_64

Steps to Produce Error:

I'm observing two behaviours that I suspect are defects:

1- When the first request is made - which takes more than the accepted timeout - the circuit becomes opened right away. My expectation was that it would track 3 failures - per options - and only then open the circuit

2- After 3 failed attempts, if I issue a new HTTP request, this is actually executed. My expectation was that it would NOT do it and fail fast.

My code and logs below...

async function fetchBookings() {
  const res = await axios.get("http://localhost:4000/api/v1/bookings");
  return {
    bookings: res.data
  };
}

function fallBack() {
  console.log("fallback");
  return {};
}

async function getData() {
  const options = {
    timeout: 500, 
    errorThresholdPercentage: 50, 
    resetTimeout: 30000, 
    maxFailures: 3
  };

  const breaker = circuitBreaker(fetchBookings, options);
  breaker.fallback(fallBack); // runs on every failure

  breaker.on("success", () => console.log("success"));
  breaker.on("failure", () => console.log("failed"));
  breaker.on("timeout", () => console.log("timed out"));
  breaker.on("reject", () => console.log("rejected"));
  breaker.on("open", () => console.log("opened"));
  breaker.on("halfOpen", () => console.log("halfOpened"));
  breaker.on("close", () => console.log("closed"));

  return breaker.fire().catch(console.error);
};

Logs

-- the First Request is made --
timed out
failed
opened
fallback
-- the Second Request is made --
timed out
failed
opened
fallback
-- the Third Request is made --
timed out
failed
opened
fallback
-- the Fourth Request is made --
timed out
failed
opened
fallback
-- after 30 seconds --
halfOpened
halfOpened

@lance
Copy link
Member

lance commented Apr 22, 2019

Hi @sesteva thanks for your report. I haven't really dug into it yet, or reproduced it locally (I've been on vacation the last week :). But at first glance, I notice that you are using both maxFailures AND errorThresholdPercentage. These are meant to be (though not documented as) mutually exclusive. The maxFailures option is currently deprecated, and will likely be removed in the next major version release. I wonder if having both of these set is the cause of the admittedly strange behavior you are seeing. Can you try to remove either maxFailures or errorThresholdPercentage from the options and see how that affects your test case?

@lance
Copy link
Member

lance commented Apr 24, 2019

@sesteva just checking in on this. Have you had an opportunity to test my theory above?

@lance
Copy link
Member

lance commented Apr 25, 2019

@sesteva I am going to close this. I have confirmed that you just need to remove maxFailures. I also recommend creating the circuit outside of getData(), otherwise a new one is created every time you call the function.

I modified your code to look like this for my testing:

const circuitBreaker = require('.');

const options = {
  timeout: 500,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
};

const breaker = circuitBreaker(fetchBookings, options);
breaker.fallback(fallBack); // runs on every failure

breaker.on('success', () => console.log('success'));
breaker.on('failure', e => console.log('failed', e));
breaker.on('timeout', e => console.log('timed out', e));
breaker.on('reject', e => console.log('rejected', e));
breaker.on('open', () => console.log('opened'));
breaker.on('halfOpen', () => console.log('halfOpened'));
breaker.on('close', () => console.log('closed'));

let num = 0;
let sleepTime = 100;
let sleepIncrement = 100;
async function fetchBookings () {
  console.log(`request ${++num}`);
  if (sleepTime > 700) sleepTime = 0;
  return new Promise(resolve => {
    setTimeout(resolve,
      sleepTime += sleepIncrement, `${num} complete\n--`);
  });
}

function fallBack () {
  return 'fallback';
}

async function getData () {
  return breaker.fire().catch(console.error);
}

getData()
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log)
  .then(getData)
  .then(console.log)
  .catch(console.log);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants