Code Monkey home page Code Monkey logo

opossum's Introduction

opossum

Node.js CI Coverage Status Known Vulnerabilities Dependency Status

Opossum is a Node.js circuit breaker that executes asynchronous functions and monitors their execution status. When things start failing, opossum plays dead and fails fast. If you want, you can provide a fallback function to be executed when in the failure state.

For more about the circuit breaker pattern, there are lots of resources on the web - search it! Fowler's blog post is one place to start reading.

Project Info
License: Apache-2.0
Documentation: https://nodeshift.dev/opossum/
Typings: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/opossum
Issue tracker: https://github.com/nodeshift/opossum/issues
Engines: Node.js >= 16

Usage

Let's say you've got an API that depends on something that might fail - a network operation, or disk read, for example. Wrap those functions up in a CircuitBreaker and you have control over your destiny.

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);

AbortController support

You can provide an AbortController (https://developer.mozilla.org/en-US/docs/Web/API/AbortController, https://nodejs.org/docs/latest/api/globals.html#globals_class_abortcontroller) for aborting on going request upon reaching Opossum timeout.

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);

Fallback

You can also provide a fallback function that will be executed in the event of failure. To take some action when the fallback is performed, listen for the fallback event.

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));

Once the circuit has opened, a timeout is set based on options.resetTimeout. When the resetTimeout expires, opossum will enter the halfOpen state. Once in the halfOpen state, the next time the circuit is fired, the circuit's action will be executed again. If successful, the circuit will close and emit the close event. If the action fails or times out, it immediately re-enters the open state.

When a fallback function is triggered, it's considered a failure, and the fallback function will continue to be executed until the breaker is closed.

The fallback function accepts the same parameters as the fire function:

const delay = (delay, a, b, c) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, delay);
  });

const breaker = new CircuitBreaker(delay);
breaker.fire(20000, 1, 2, 3);
breaker.fallback((delay, a, b, c) => `Sorry, out of service right now. But your parameters are: ${delay}, ${a}, ${b} and ${c}`);

Breaker State Initialization

There may be times where you will need to initialize the state of a Circuit Breaker. Primary use cases for this are in a serverless environment such as Knative or AWS Lambda, or any container based platform, where the container being deployed is ephemeral.

The toJSON method is a helper function to get the current state and status of a breaker:

const breakerState = breaker.toJSON();

This will return an object that might look similar to this:

{
  state: {
    enabled: true,
    name: 'functionName'
    closed: true,
    open: false,
    halfOpen: false,
    warmUp: false,
    shutdown: false
  },
  status: {
    ...
  }
};

A new circuit breaker instance can be created with this state by passing this object in:

const breaker = new CircuitBreaker({state: state});

Status Initialization

There may also be times where you will need to pre-populate the stats of the Circuit Breaker Status Object. Primary use cases for this are also in a serverless environment such as Knative or AWS Lambda, or any container based platform, where the container being deployed is ephemeral.

Getting the existing cumulative stats for a breaker can be done like this:

const stats = breaker.stats;

stats will be an object that might look similar to this:

{
  failures: 11,
  fallbacks: 0,
  successes: 5,
  rejects: 0,
  fires: 16,
  timeouts: 0,
  cacheHits: 0,
  cacheMisses: 0,
  semaphoreRejections: 0,
  percentiles: {
    '0': 0,
    '1': 0,
    '0.25': 0,
    '0.5': 0,
    '0.75': 0,
    '0.9': 0,
    '0.95': 0,
    '0.99': 0,
    '0.995': 0
  },
  latencyTimes: [ 0 ],
  latencyMean: 0
}

To then re-import those stats, first create a new Status object with the previous stats and then pass that as an option to the CircuitBreaker constructor:

const statusOptions = {
  stats: {....}
};

const newStatus = CircuitBreaker.newStatus(statusOptions);

const breaker = new CircuitBreaker({status: newStatus});

Browser

Opossum really shines in a browser. You can use it to guard against network failures in your AJAX calls.

We recommend using webpack to bundle your applications, since it does not have the effect of polluting the window object with a global. However, if you need it, you can access a circuitBreaker function in the global namespace by doing something similar to what is shown in the below example.

Here is an example using hapi.js. See the opossum-examples repository for more detail.

Include opossum.js in your HTML file.

<html>
<head>
  <title>My Super App</title>
  <script type='text/javascript' src="/jquery.js"></script>
  <script type='text/javascript' src="/opossum.js"></script>
  <script type='text/javascript' src="/app.js"></script>
<body>
...
</body>
</head>
</html>

In your application, set a route to the file, pointing to node_modules/opossum/dist/opossum-min.js.

// server.js
const server = new Hapi.Server();
server.register(require('inert', (err) => possibleError(err)));
server.route({
  method: 'GET',
  path: '/opossum.js',
  handler: {
    file: {
      path: path.join(__dirname, 'node_modules', 'opossum', 'dist', 'opossum-min.js'),
    }
  }
});

In the browser's global scope will be a CircuitBreaker constructor. Use it to create circuit breakers, guarding against network failures in your REST API calls.

// 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)));
});

Events

A CircuitBreaker will emit events for important things that occur. Here are the events you can listen for.

  • fire - emitted when the breaker is fired.
  • reject - emitted when the breaker is open (or halfOpen).
  • timeout - emitted when the breaker action times out.
  • success - emitted when the breaker action completes successfully
  • failure - emitted when the breaker action fails, called with the error
  • open - emitted when the breaker state changes to open
  • close - emitted when the breaker state changes to closed
  • halfOpen - emitted when the breaker state changes to halfOpen
  • fallback - emitted when the breaker has a fallback function and executes it
  • semaphoreLocked - emitted when the breaker is at capacity and cannot execute the request
  • healthCheckFailed - emitted when a user-supplied health check function returns a rejected promise
  • shutdown - emitted when the breaker shuts down

Handling events gives a greater level of control over your application behavior.

const breaker = new CircuitBreaker(() => $.get(route), circuitBreakerOptions);

breaker.fallback(() => ({ body: `${route} unavailable right now. Try later.` }));

breaker.on('success',
  (result) => $(element).append(
    makeNode(`SUCCESS: ${JSON.stringify(result)}`)));

breaker.on('timeout',
  () => $(element).append(
    makeNode(`TIMEOUT: ${route} is taking too long to respond.`)));

breaker.on('reject',
  () => $(element).append(
    makeNode(`REJECTED: The breaker for ${route} is open. Failing fast.`)));

breaker.on('open',
  () => $(element).append(
    makeNode(`OPEN: The breaker for ${route} just opened.`)));

breaker.on('halfOpen',
  () => $(element).append(
    makeNode(`HALF_OPEN: The breaker for ${route} is half open.`)));

breaker.on('close',
  () => $(element).append(
    makeNode(`CLOSE: The breaker for ${route} has closed. Service OK.`)));

breaker.on('fallback',
  (data) => $(element).append(
    makeNode(`FALLBACK: ${JSON.stringify(data)}`)));

Promises vs. Callbacks

The opossum API returns a Promise from CircuitBreaker.fire(). But your circuit action - the async function that might fail - doesn't have to return a promise. You can easily turn Node.js style callback functions into something opossum understands by using the built in Node core utility function util.promisify() .

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);

And just for fun, your circuit doesn't even really have to be a function. Not sure when you'd use this - but you could if you wanted to.

const breaker = new CircuitBreaker('foo', options);

breaker.fire()
  .then(console.log) // logs 'foo'
  .catch(console.error);

Calculating errorThresholdPercentage

The errorThresholdPercentage value is compared to the error rate. That rate is determined by dividing the number of failures by the number of times the circuit has been fired. You can see this comparison here:

// check stats to see if the circuit should be opened
  const stats = circuit.stats;
  if ((stats.fires < circuit.volumeThreshold) && !circuit.halfOpen) return;
  const errorRate = stats.failures / stats.fires * 100;
  if (errorRate > circuit.options.errorThresholdPercentage || circuit.halfOpen) {
    circuit.open();
  }

The numbers for fires and failures come from the stats that are indeed governed by rollingCountTimeout and rollingCountBuckets. The timeout value is the total number of seconds for which the stats are being maintained, and the buckets value is the number of slots in the window. The defaults are 10 seconds and 10 buckets. So, the statistics that are being compared against errorThresholdPercentage are based on 10 samples, one per second over the last 10 seconds.

Example: a circuit is fired 24 times over 10 seconds with a somewhat bursty pattern, failing three times.

| fires: 2 | fires: 1 | fires: 3 | fires: 0 | fires: 9 | fires: 3 | fires: 2 | fires: 0 | fires: 4 | fires: 0 |
| fails: 0 | fails: 0 | fails: 0 | fails: 0 | fails: 0 | fails: 3 | fails: 0 | fails: 0 | fails: 0 | fails: 0 |

The failure rate here is 3/24 or 1/8 or 12.5%. The default error threshold is 50%, so in this case, the circuit would not open. However, if you modified the rollingCountTimeout to 3 seconds, and the rollingCountBuckets to 3 (not recommended), then the stats array might look like these three seconds from above.

| fires: 3 | fires: 2 | fires: 0 |
| fails: 3 | fails: 0 | fails: 0 |

Now, without changing errorThresholdPercentage our circuit will open because our error rate is now 3/5 or 60%. It's tricky to test this stuff because the array of statistics is a rolling count. Every second the oldest bucket is removed and a new one is added, so the totals change constantly in a way that may not be intuitive.

For example, if the first example is shifted right, dropping the first bucket and adding another with fires: 3 the total number of fires now in the stats is not 27 (24+3) but 25 (24-2+3).

The code that is summing the stats samples is here:

  const totals = this[WINDOW].reduce((acc, val) => {
    if (!val) { return acc; }
    Object.keys(acc).forEach(key => {
      if (key !== 'latencyTimes' && key !== 'percentiles') {
        (acc[key] += val[key] || 0);
      }
    });

    if (this.rollingPercentilesEnabled) {
      acc.latencyTimes.push.apply(acc.latencyTimes, val.latencyTimes || []);
    }
    return acc;
  }, bucket());

Typings

Typings are available here.

If you'd like to add them, run npm install @types/opossum in your project.

Metrics

Prometheus

The opossum-prometheus module can be used to produce metrics that are consumable by Prometheus. These metrics include information about the circuit itself, for example how many times it has opened, as well as general Node.js statistics, for example event loop lag.

Hystrix

The opossum-hystrix module can be used to produce metrics that are consumable by the Hystrix Dashboard.

Troubleshooting

You may run into issues related to too many listeners on an EventEmitter like this.

(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 10 unpipe listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 finish listeners added. Use emitter.setMaxListeners() to increase limit

In some cases, seeing this error might indicate a bug in client code, where many CircuitBreakers are inadvertently being created. But there are legitimate scenarios where this may not be the case. For example, it could just be that you need more than 10 CircuitBreakers in your app. That's ok.

To get around the error, you can set the number of listeners on the stream.

breaker.stats.getHystrixStream().setMaxListeners(100);

Or it could be that you have a large test suite which exercises some code that creates CircuitBreakers and does so repeatedly. If the CircuitBreaker being created is only needed for the duration of the test, use breaker.shutdown() when the circuit is no longer in use to clean up all listeners.

opossum's People

Contributors

aalykiot avatar dependabot[bot] avatar duartemendes avatar elderbas avatar github-actions[bot] avatar gjethwani avatar greenkeeper[bot] avatar helio-frota avatar hongbo-miao avatar iamelevich avatar jdmarshall avatar kunal15595 avatar lance avatar lholmquist avatar mastermatt avatar mert574 avatar mhdawson avatar mikeybyker avatar pacostas avatar quinnlangille avatar raytung avatar richardlau avatar richdouglasevans avatar scttcper avatar snyk-bot avatar stephenkubovic avatar tajpouria avatar tavogel avatar tiagofabre avatar tjenkinson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

opossum's Issues

Support infinite retries - maxFailures = 0?

Hello,

We have some situations where we want to just keep retrying, and ideally it could be done programmatically inside opossum. Currently we have to write some wrapper code that checks if we want infinity, and retry ourselves manually.

Electron example

Write an example of using opossum in a desktop app using electron.
Can be based on existing examples.

Remove Fidelity promises

I don't think there's any reason to use a Promise implementation that is non-native. As long as native promises work with node 4-6, we should just use them. If some portion of the promise API is not available on Node 4, we can maybe do a version check?

Build error on Windows 10

Node.js Version:
8.9.0
Operating System:
Windows 10
Steps to Produce Error:
npm it

$ npm it
npm WARN registry Unexpected warning for https://registry.npmjs.org/: Miscellaneous Warning EINTEGRITY: sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA== integrity checksum failed when using sha512: wanted sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA== but got sha1-9qn+xBzFCh3lD6M2A6tYCZH2Bo4=. (23892 bytes)
npm WARN registry Using stale package data from https://registry.npmjs.org/ due to a request error during revalidation.

> [email protected] postinstall C:\Users\hf\Desktop\dev\opossum\node_modules\electron
> node install.js

npm WARN prepublish-on-install As of npm@5, `prepublish` scripts are deprecated.
npm WARN prepublish-on-install Use `prepare` for build steps and `prepublishOnly` for upload-only.
npm WARN prepublish-on-install See the deprecation note in `npm help scripts` for more information.

> [email protected] prepublish C:\Users\hf\Desktop\dev\opossum
> nsp check && npm run build:browser

(+) No known vulnerabilities found

> [email protected] build:browser C:\Users\hf\Desktop\dev\opossum
> browserify index.js lib/*.js > dist/opossum.js

Error: Cannot find module 'C:\Users\hf\Desktop\dev\opossum\lib\*.js' from 'C:\Users\hf\Desktop\dev\opossum'
    at C:\Users\hf\Desktop\dev\opossum\node_modules\resolve\lib\async.js:55:21
    at load (C:\Users\hf\Desktop\dev\opossum\node_modules\resolve\lib\async.js:69:43)
    at onex (C:\Users\hf\Desktop\dev\opossum\node_modules\resolve\lib\async.js:92:31)
    at C:\Users\hf\Desktop\dev\opossum\node_modules\resolve\lib\async.js:22:47
    at FSReqWrap.oncomplete (fs.js:152:21)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build:browser: `browserify index.js lib/*.js > dist/opossum.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build:browser script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\hf\AppData\Roaming\npm-cache\_logs\2017-11-10T10_54_48_577Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] prepublish: `nsp check && npm run build:browser`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] prepublish script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\hf\AppData\Roaming\npm-cache\_logs\2017-11-10T10_54_49_041Z-debug.log

Related to bucharest-gold/entente#179

Flaws in handling of HALF_OPEN state leading to ineffectual circuit-breaker under persistent failure conditions

When _startTimer puts STATE into HALF_OPEN, this means that the circuit no longer registers as opened.

This means in fire when it checks if (this.opened && !this.pendingClose), it doesn't just let the single next request through, but it opens the flood-gates to all new requests passing through un-rejected.

Suggestion is to fix change this to if (!this.closed && !this.pendingClose).

When this is fixed , a new problem will surface, which is that if _startTimer fires, lets a single request through, and that request fails, it is quite likely that the failure rate has dropped below the threshold meaning that fail() will not re-issue a circuit.open() and so the timer will never trigger again meaning that all requests remain perpetually rejected.

I'd suggest that for this problem, amending the error-rate conditional in fail() from:
if (errorRate > ... || circuit.options.maxFailures >= stats.failures) {
to:
if (errorRate > ... || ... >= stats.failures || circuit.halfOpen) {
So that any failure in a half-open state causes the circuit to re-open and eventually make another attempt at closing.

Add a health check function

Opossum should support user-provided health check functions. Something like:

circuit.on('healthCheck', (status) => {
  // user-supplied logic
  // return a promise
});

This means we would also need an option for how long this interval should be. And of course, we need to actually perform a health check periodically.

setInterval(_ => 
  _healthCheck().catch((err) => circuit.emit('healthCheckFailed', err));

Or something like that.

Use semaphore isolation to limit concurrent requests

Fine Grained Bulkhead Pattern

The goal here is to implement a fine-grained bulkhead pattern similar to how Hystrix works in Java. This works hand-in-hand with circuit breakers to provide user-configurable client-side load balancing / throttling and helps to mitigate DoS attacks.

Before actually executing any of the circuit breaker wrapped code, the fire() function should acquire a semaphore, execute the function, and then release the semaphore. This prevents a client from unintentionally overloading a server with requests to a single resource, protecting against both programmer error (e.g. a loop that just spins, firing request after request), or malicious code designed to bring a service down.

Things to Consider

  • We need to find a reasonable default number of concurrent connections.
  • We need to decide if we should check the semaphore first, or if we should check the open state first. If the circuit is open, we are essentially throttling anyway, so we don't need to acquire a semaphore, and we're not using any additional external resources because we are failing fast.
  • Does this limit also affect the fallback functions? If we decide that we check open state first, and potentially fallback, should the fallback functions be subject to the same rate limiting behavior.

Should fallback be applied when circuit is closed?

This is not a bug per say, but I'm also not sure this is the correct behavior.

Shouldn't the fallback function only be applied when the circuit is open or half open?

I'm using this package to make http requests to several upstreams. And having the fallback being called when the fire action fails means that we lose the actual response from the upstream.

My suggestion is to invoke the fallback function only when the event 'reject' is also emitted. This will allow us to propagate the actual upstream response to above. Since we already have the response we might as well use it for debug and monitoring purposes.

Fallback is being called twice

Considering that the circuit is closed and that the fallback function always returns a promise rejection: If the action triggered by the breaker is rejected and it took longer than the breaker timeout, the fallback function will be called twice.

First time it's called because the breaker timeout is achieved. Second time it's called because the action was finished with a rejection.

Due to the second call, we get the following error:

(node:14582) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): StatusCodeError: 500 - {"statusCode":500,"message":"Server error"}
(node:14582) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Node.js Version: 8.9.4

Operating System: Darwin DuarteMendes.local 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 x86_64

Steps to Produce Error:

const circuitBreaker = require('opossum');

async function forceFallbackToBeExecutedTwiceOnTheSameExecution () {
    const breaker = circuitBreaker(timerBasedFunction, { timeout: 3500 });

    breaker.fallback((timer, err) => Promise.reject(err));
    breaker.on('fallback', () => console.log('Fallback was called!'));

    [0, 1, 2, 3, 4].forEach(async function (i) {
        try {
            const result = await breaker.fire(i * 1000);
            console.log(result);
        } catch (err) {
            console.log(err);
        }
    });
}

function timerBasedFunction (timer) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (timer >= 4000) {
                return reject(`Error for the ${timer}ms timer`);
            }

            return resolve(`Result for the ${timer}ms timer`);
        }, timer);
    });
}

forceFallbackToBeExecutedTwiceOnTheSameExecution();

Result:

Result for the 0ms timer
Result for the 1000ms timer
Result for the 2000ms timer
Result for the 3000ms timer
Fallback was called!
{ Error: Timed out after 3500ms
    at Timeout.setTimeout (/Users/duartemendes/projects/testing-opossum/node_modules/opossum/lib/circuit.js:322:15)
    at ontimeout (timers.js:475:11)
    at tryOnTimeout (timers.js:310:5)
    at Timer.listOnTimeout (timers.js:270:5) code: 'ETIMEDOUT' }
Fallback was called!
(node:35201) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error for the 4000ms timer
(node:35201) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Replace prepublish npm target with 'prepare'

npm WARN prepublish-on-install As of npm@5, `prepublish` scripts are deprecated.
npm WARN prepublish-on-install Use `prepare` for build steps and `prepublishOnly` for upload-only.
npm WARN prepublish-on-install See the deprecation note in `npm help scripts` for more information.

Node.js Version: 9.0.0

Operating System: Darwin Callisto.local 16.7.0 Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64 x86_64

Steps to Produce Error: make ci

Promise Rejection in fallback

Can i do something like this ?

function getCache(req, res) {
  return getCacheCB.fire(req.params.userId, req.headers.referer)
    .then(resObj => res.status(resObj.statusCode).send(
        userProfileSerializer(resObj.message, '1.0.0')))
    .catch(resObj => res.status(resObj.statusCode).send(errorSerializer(resObj, '1.0.0')));
}

getCacheCB.fallback((userId, referer) => {
  log.info('Get cache fallback'); // eslint-disable-line
  return service.fetchUserProfile(userId, referer)
});

Looks like Promise.reject does not seems to work.
I get this warning in the console when the service.fetchUserProfile(userId, referer) function returns promise rejection

(node:78791) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): StatusCodeError: 504 - "<html>\r\n<head><title>504 Gateway Time-out</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>504 Gateway Time-out</h1></center>\r\n<hr><center>openresty/1.9.7.3</center>\r\n</body>\r\n</html>\r\n"

Performance issues

Node.js Version:
node v8.9.4
Operating System:
Linux 4.14.0-deepin2-amd64 #1 SMP PREEMPT Deepin 4.14.12-2 (2018-01-06) x86_64 GNU/Linux

Steps to Produce Error:
I have a component A that calls a component B using an http request.
When I benchmark this action using ab, without opossum, the average time per request is 50ms.
When I wrap the http request into opossum and I benchmark this action with ab, the average time per request increases exponentially after each request and errors are printed to console :

(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 101 unpipe listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 101 drain listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 101 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 101 close listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 101 finish listeners added. Use emitter.setMaxListeners() to increase limit

The ab command I use :

ab -n 10000 -c 100 http://localhost:3000/check1

Can you help me please ?

Make CircuitBreaker an EventEmitter

The breaker should emit the following events:

  • execute || fire - emitted when the breaker action is executed.
  • reject - when a breaker is fired, but it's either open or half-open
  • timeout - when a fired action times out
  • failure - when a fired action fails
  • success - when a fired action succeeds
  • open - when the breaker is opened
  • close - when the breaker is closed
  • halfOpen - when the breaker is half open

[FEATURE REQUEST] Aggregate circuit stats

In order to see all the metrics of the circuits on Hystrix Dashboard, we need a way to aggregate all the stats of the circuits created in one stream.

This is just a feature request based on issue #124

Stuck in an open circuit breaker state

While i was recording the screencast showing how to use opossum, i sort of got myself stuck in an always opened state.

I'll try to reproduce this with a test, if not i can do a screencast of what i encountered.

Add caching capability to circuits

Our needs are not exactly the same as those of Hystrix, however that project does provide a good reference point.

A possible approach for this feature might look something like this.

  • Caching is off by default. When a circuit is constructed, one of the options should be to turn caching on. Everything that follows assumes caching is enabled.
  • Each circuit created is a key into a module-scoped WeakMap. The values are the returned results of the circuit breaker's action.
const CACHE = new WeakMap();

class CircuitBreaker() {
  CACHE.set(this, undefined);
}
  • When the circuit is fired, the map is checked first to determine if there is a cached value. If so, the cached value is immediately returned, before any checking of circuit state/fallback etc.
function fire () {
  if (CACHE.get(this) !== undefined) return CACHE.get(this);
  // .... etc.
}
  • Circuit statistics should include cache hits/misses.
  • A user should be able to clear the cache for a circuit.
class CircuitBreaker () {
  clearCache () {
    CACHE.set(this, undefined);
  }
}

This would only be useful for circuit breakers that access a fixed URL (e.g. no path parameters, or POSTs), for which the data rarely changes. E.g. A user profile.

OPEN QUESTION: Should the cache periodically reset itself? E.g. each circuit sets a timeout interval of 3 minutes, and invalidates its cache entry. If so, this timeout value should be user configurable. This would allow for situations where a user may have updated their profile from the previous example asynchronously. Instead of requiring the user to write code detecting a change, and then clearing the circuit's cache, since a change like this is not terribly important to update in real time, they can just wait for the cache entry to become invalidated after the timeout interval.

Enable/disable circuit

Hey, assuming that we could enable/disable some circuits, is it a good idea to put a parameter to enable/disable it in the options?

An use case is that I wouldn't like to see my circuits working on dev/test/stage environments (unless that I want to force it to test some thoughts).

Node.js Version: v8.6.0

Operating System: Ubuntu 14.04

Make a circuit's stats rolling

When generating stats for the Hystrix dashboard, much of them are rolling numbers that reset after a configurable amount of time. This makes sense because if you're monitoring an application's performance, you probably don't care about the 5000 failures that happened yesterday, because you fixed that problem already with a hotfix - but if your stats aren't rolling, then you'll keep seeing that 5000 failure number until the app with the circuit is restarted

Get HystrixStats

Is there any way of getting HystrixStats to open the stream url without opening a circuit?
I Tried the folowing code but it give's me an error "TypeError: Cannot read property 'name' of undefined"

const circuitBreaker = require('opossum');
const circuit = circuitBreaker().circuit;
const hystrixStats = circuit.hystrixStats;
module.exports = (app) => {
  Verification = async (req, res, next) => {
    res.writeHead(200, {'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive'});
    res.write('retry: 10000\n');
    res.write('event: connecttime\n');
  
    hystrixStats.getHystrixStream().pipe(res);
  }
}

Fix unhandled promise rejection warnings in tests

When running the tests some warnings are generated. There are quite a few. Here's an example.

 CircuitBreaker fallback as a rejected promise

    ✔ should be equal
(node:93470) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 8): Error: nope

Opossum with Angular 5

Node.js Version:
8.9.3

Operating System:
Windows 10

Steps to Produce Error:
I created an Angular application with Angular-cli and added opossum in it.
Then, tried to make a http request with opossum to https://jsonplaceholder.typicode.com/, copied and pasted the code on readme into my application, but it throws me this error:

ERROR TypeError: opossum_1.default is not a function
    at AppComponent.ngOnInit (app.component.ts:28)
    at checkAndUpdateDirectiveInline (core.js:12411)
    at checkAndUpdateNodeInline (core.js:13935)
    at checkAndUpdateNode (core.js:13878)
    at debugCheckAndUpdateNode (core.js:14771)
    at debugCheckDirectivesFn (core.js:14712)
    at Object.eval [as updateDirectives] (AppComponent_Host.ngfactory.js? [sm]:1)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:14697)
    at checkAndUpdateView (core.js:13844)
    at callWithDebugContext (core.js:15098)

I'll paste the code below

export class AppComponent implements OnInit {
  title = 'app';

  private async makeRequest () {
    return await this.http.get(`https://jsonplaceholder.typicode.com/posts/1`).toPromise();
  }

  constructor(private http: HttpClient) {}

  ngOnInit() {
    const route = 'https://jsonplaceholder.typicode.com/posts/1';
    const circuitBreakerOptions = {
      timeout: 500,
      maxFailures: 3,
      resetTimeout: 5000
    };
    const circuit = circuitBreaker(this.makeRequest, circuitBreakerOptions);
    circuit.fallback(() => `unavailable right now. Try later.`);
    circuit.on('success', (result) => JSON.stringify(result));
    circuit.fire().catch((e) => console.error(e));
  }
}

An in-range update of eslint-plugin-import is breaking the build 🚨

Version 2.4.0 of eslint-plugin-import just got published.

Branch Build failing 🚨
Dependency eslint-plugin-import
Current Version 2.3.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As eslint-plugin-import is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪

Status Details
  • continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Commits

The new version differs by 10 commits.

  • 44ca158 update utils changelog
  • a3728d7 bump eslint-module-utils to v2.1.0
  • 3e29169 bump v2.4.0
  • ea9c92c Merge pull request #737 from kevin940726/master
  • 8f9b403 fix typos, enforce type of array of strings in allow option
  • 95315e0 update CHANGELOG.md
  • 28e1623 eslint-module-utils: filePath in parserOptions (#840)
  • 2f690b4 update CI to build on Node 6+7 (#846)
  • 7d41745 write doc, add two more tests
  • dedfb11 add allow glob for rule no-unassigned-import, fix #671

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.