Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Opossum is a Node.js package that implements the circuit breaker pattern. It helps in making your application more resilient by preventing cascading failures and providing fallback mechanisms when external services fail or become unresponsive.
Circuit Breaker
This feature allows you to wrap a function that might fail with a circuit breaker. If the function fails too many times, the circuit breaker will open and prevent further calls to the failing function until it recovers.
const CircuitBreaker = require('opossum');
async function asyncFunctionThatCouldFail() {
// Simulate a function that could fail
return 'Success!';
}
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail);
breaker.fire()
.then(console.log)
.catch(console.error);
Fallback
This feature allows you to specify a fallback function that will be called when the main function fails. This can be useful for providing a default response or alternative behavior when the primary function is unavailable.
const CircuitBreaker = require('opossum');
async function asyncFunctionThatCouldFail() {
throw new Error('Failed!');
}
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, {
fallback: () => 'Fallback response'
});
breaker.fire()
.then(console.log)
.catch(console.error);
Status Monitoring
This feature allows you to monitor the status of the circuit breaker. You can listen for events such as 'open', 'halfOpen', and 'close' to take appropriate actions based on the state of the circuit breaker.
const CircuitBreaker = require('opossum');
async function asyncFunctionThatCouldFail() {
return 'Success!';
}
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail);
breaker.on('open', () => console.log('Circuit breaker opened!'));
breaker.on('halfOpen', () => console.log('Circuit breaker half-open!'));
breaker.on('close', () => console.log('Circuit breaker closed!'));
breaker.fire()
.then(console.log)
.catch(console.error);
Cockatiel is a resilience library for JavaScript and TypeScript that provides policies for retries, timeouts, and circuit breakers. Compared to Opossum, Cockatiel offers a more comprehensive set of resilience policies and is designed to work seamlessly with TypeScript.
Promise-breaker is a lightweight library that provides a simple implementation of the circuit breaker pattern for promises. It is less feature-rich compared to Opossum but can be a good choice for simpler use cases where only basic circuit breaker functionality is needed.
Brakes is a Node.js library that provides a robust implementation of the circuit breaker pattern along with additional features like bulkheading and fallback mechanisms. It is similar to Opossum in terms of functionality but offers more advanced features for handling complex failure scenarios.
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 >= 10 |
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);
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}`);
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});
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 cumalative 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});
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)));
});
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 successfullyfailure
- emitted when the breaker action fails, called with the erroropen
- 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 itsemaphoreLocked
- emitted when the breaker is at capacity and cannot execute the requesthealthCheckFailed
- emitted when a user-supplied health check function returns a rejected promiseHandling 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)}`)));
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);
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 ||
stats.failures >= circuit.options.maxFailures ||
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: 8 | 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 are available here.
If you'd like to add them, run npm install @types/opossum
in your project.
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.
The opossum-hystrix
module can
be used to produce metrics that are consumable by the Hystrix Dashboard.
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 CircuitBreaker
s 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 CircuitBreaker
s 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 CircuitBreaker
s 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.
FAQs
A fail-fast circuit breaker for promises and callbacks
The npm package opossum receives a total of 226,480 weekly downloads. As such, opossum popularity was classified as popular.
We found that opossum demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.