diff --git a/lib/circuit.js b/lib/circuit.js index 86228a8b..38c9aa01 100644 --- a/lib/circuit.js +++ b/lib/circuit.js @@ -27,12 +27,16 @@ const NAME = Symbol('name'); * opening. * @param options.resetTimeout The time in milliseconds to wait before setting * the breaker to `halfOpen` state, and trying the action again. + * @param options.rollingCountTimeout Sets the duration of the statistical + * rolling window, in milliseconds. This is how long Opossum keeps metrics for + * the circuit breaker to use and for publishing. Default: 10000 * @fires CircuitBreaker#halfOpen */ class CircuitBreaker extends EventEmitter { constructor (action, options) { super(); this.options = options; + this.options.rollingCountTimeout = options.rollingCountTimeout || 10000; this.Promise = options.Promise; this[STATUS] = new Status(this); this[STATE] = CLOSED; diff --git a/lib/status.js b/lib/status.js index 5b567acc..84cc89de 100644 --- a/lib/status.js +++ b/lib/status.js @@ -9,30 +9,7 @@ const CIRCUIT_BREAKER = Symbol('circuit-breaker'); */ class Status { constructor (circuit) { - /** - * The number of times the breaker's action has failed - */ - this.failures = 0; - /** - * The number of times a fallback function has been executed - */ - this.fallbacks = 0; - /** - * The number of times the action for this breaker executed successfully - */ - this.successes = 0; - /** - * The number of times this breaker been rejected because it was fired, but in the open state. - */ - this.rejects = 0; - /** - * The number of times this circuit breaker has been fired - */ - this.fires = 0; - /** - * The number of times this circuit breaker has timed out - */ - this.timeouts = 0; + reset(this); this[CIRCUIT_BREAKER] = circuit; circuit.on('success', () => this.successes++); circuit.on('failure', () => this.failures++); @@ -40,7 +17,37 @@ class Status { circuit.on('timeout', () => this.timeouts++); circuit.on('fire', () => this.fires++); circuit.on('reject', () => this.rejects++); + const interval = setInterval( + () => reset(this), circuit.options.rollingCountTimeout); + if (typeof interval.unref === 'function') interval.unref(); } } +function reset (status) { + /** + * The number of times the breaker's action has failed + */ + status.failures = 0; + /** + * The number of times a fallback function has been executed + */ + status.fallbacks = 0; + /** + * The number of times the action for this breaker executed successfully + */ + status.successes = 0; + /** + * The number of times this breaker been rejected because it was fired, but in the open state. + */ + status.rejects = 0; + /** + * The number of times this circuit breaker has been fired + */ + status.fires = 0; + /** + * The number of times this circuit breaker has timed out + */ + status.timeouts = 0; +} + module.exports = exports = Status; diff --git a/test/test.js b/test/test.js index 9c4d4dbd..a8f82907 100644 --- a/test/test.js +++ b/test/test.js @@ -294,6 +294,24 @@ test('CircuitBreaker status', (t) => { }); }); +test('CircuitBreaker rolling counts', (t) => { + const breaker = cb(passFail, { rollingCountTimeout: 100 }); + const deepEqual = (t, expected) => (actual) => t.deepEqual(actual, expected, 'expected status values'); + Fidelity.all([ + breaker.fire(10).then(deepEqual(t, 10)), + breaker.fire(20).then(deepEqual(t, 20)), + breaker.fire(30).then(deepEqual(t, 30)) + ]) + .then(() => + t.deepEqual(breaker.status.successes, 3, 'breaker succeeded 3 times')) + .then(() => { + setTimeout(() => { + t.deepEqual(breaker.status.successes, 0, 'breaker reset stats'); + t.end(); + }, 100); + }); +}); + test('CircuitBreaker fallback event', (t) => { t.plan(1); const breaker = cb(passFail, {maxFailures: 0});