From f9c6fc69e6e24043e18ff1de7d28566207ee7b3d Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Fri, 25 Aug 2023 11:18:00 -0700 Subject: [PATCH 01/17] feat: add enableSnapshots description --- lib/circuit.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/circuit.js b/lib/circuit.js index 69c21ac0..5016819f 100644 --- a/lib/circuit.js +++ b/lib/circuit.js @@ -98,6 +98,10 @@ Please use options.errorThresholdPercentage`; * @param {AbortController} options.abortController this allows Opossum to * signal upon timeout and properly abort your on going requests instead of * leaving it in the background + * @param {boolean} options.enableSnapshots whether to enable the rolling + * stats snapshots that opossum emits at the bucketInterval. Disable this + * as an optimization if you don't listen to the 'snapshot' event to reduce + * the number of timers opossum initiates. * * * @fires CircuitBreaker#halfOpen From 60dafdb124c2e474b9936ba506ebb52a29c3c3a5 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Fri, 25 Aug 2023 11:29:02 -0700 Subject: [PATCH 02/17] feat: add EventEmitteR --- lib/circuit.js | 6 ++++++ lib/status.js | 15 +++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/circuit.js b/lib/circuit.js index 5016819f..61a26785 100644 --- a/lib/circuit.js +++ b/lib/circuit.js @@ -102,6 +102,11 @@ Please use options.errorThresholdPercentage`; * stats snapshots that opossum emits at the bucketInterval. Disable this * as an optimization if you don't listen to the 'snapshot' event to reduce * the number of timers opossum initiates. + * @param {EventEmitter} options.rotateBucketController if you have multiple + * breakers in your app, the number of timers across breakers can get costly. + * This option allows you to provide an EventEmitter that rotates the buckets + * so you can have one global timer in your app. Make sure that you are + * emitting a 'rotate' event from this EventEmitter * * * @fires CircuitBreaker#halfOpen @@ -163,6 +168,7 @@ class CircuitBreaker extends EventEmitter { this.options.cacheGetKey = options.cacheGetKey ?? ((...args) => JSON.stringify(args)); this.options.enableSnapshots = options.enableSnapshots !== false; + this.options.rotateBucketController = options.rotateBucketController; // Set default cache transport if not provided if (this.options.cache) { diff --git a/lib/status.js b/lib/status.js index b95c1ac0..bb8113e7 100644 --- a/lib/status.js +++ b/lib/status.js @@ -64,13 +64,17 @@ class Status extends EventEmitter { // Default this value to true this.enableSnapshots = options.enableSnapshots !== false; + // can be undefined + this.rotateBucketController = options.rotateBucketController; + // prime the window with buckets for (let i = 0; i < this[BUCKETS]; i++) this[WINDOW][i] = bucket(); - // rotate the buckets periodically const bucketInterval = Math.floor(this[TIMEOUT] / this[BUCKETS]); - this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), - bucketInterval); + // rotate the buckets based on an optional EventEmitter + if (this.rotateBucketController) this.rotateBucketController.on('rotate', nextBucket(this[WINDOW])); + // or rotate the buckets periodically + else this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), bucketInterval); // No unref() in the browser if (typeof this[BUCKET_INTERVAL].unref === 'function') { @@ -173,7 +177,10 @@ class Status extends EventEmitter { shutdown () { this.removeAllListeners(); - clearInterval(this[BUCKET_INTERVAL]); + // interval is not set if rotateBucketController is provided + if (this.rotateBucketController === undefined) { + clearInterval(this[BUCKET_INTERVAL]); + } if (this.enableSnapshots) { clearInterval(this[SNAPSHOT_INTERVAL]); } From dfdafcee61d7794bd2a762cc022eb92546abb298 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Fri, 25 Aug 2023 12:21:12 -0700 Subject: [PATCH 03/17] feat: add eventemitter option --- lib/circuit.js | 6 ++--- lib/status.js | 20 +++++++++------- test/status-test.js | 56 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/lib/circuit.js b/lib/circuit.js index 61a26785..524e40c4 100644 --- a/lib/circuit.js +++ b/lib/circuit.js @@ -99,13 +99,13 @@ Please use options.errorThresholdPercentage`; * signal upon timeout and properly abort your on going requests instead of * leaving it in the background * @param {boolean} options.enableSnapshots whether to enable the rolling - * stats snapshots that opossum emits at the bucketInterval. Disable this - * as an optimization if you don't listen to the 'snapshot' event to reduce + * stats snapshots that opossum emits at the bucketInterval. Disable this + * as an optimization if you don't listen to the 'snapshot' event to reduce * the number of timers opossum initiates. * @param {EventEmitter} options.rotateBucketController if you have multiple * breakers in your app, the number of timers across breakers can get costly. * This option allows you to provide an EventEmitter that rotates the buckets - * so you can have one global timer in your app. Make sure that you are + * so you can have one global timer in your app. Make sure that you are * emitting a 'rotate' event from this EventEmitter * * diff --git a/lib/status.js b/lib/status.js index bb8113e7..0a29ee3d 100644 --- a/lib/status.js +++ b/lib/status.js @@ -71,14 +71,18 @@ class Status extends EventEmitter { for (let i = 0; i < this[BUCKETS]; i++) this[WINDOW][i] = bucket(); const bucketInterval = Math.floor(this[TIMEOUT] / this[BUCKETS]); - // rotate the buckets based on an optional EventEmitter - if (this.rotateBucketController) this.rotateBucketController.on('rotate', nextBucket(this[WINDOW])); - // or rotate the buckets periodically - else this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), bucketInterval); - - // No unref() in the browser - if (typeof this[BUCKET_INTERVAL].unref === 'function') { - this[BUCKET_INTERVAL].unref(); + + if (this.rotateBucketController) { + // rotate the buckets based on an optional EventEmitter + this.rotateBucketController.on('rotate', nextBucket(this[WINDOW])); + } else { + // or rotate the buckets periodically + this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), + bucketInterval); + // No unref() in the browser + if (typeof this[BUCKET_INTERVAL].unref === 'function') { + this[BUCKET_INTERVAL].unref(); + } } /** diff --git a/test/status-test.js b/test/status-test.js index e4ee4c5a..1ffa5b65 100644 --- a/test/status-test.js +++ b/test/status-test.js @@ -1,5 +1,6 @@ 'use strict'; +const { EventEmitter } = require('stream'); const test = require('tape'); const CircuitBreaker = require('../'); const Status = require('../lib/status.js'); @@ -194,3 +195,58 @@ test('CircuitBreaker status - enableSnapshots is false in Status when set to fal breaker.shutdown(); t.end(); }); + +test('CircuitBreaker status - breaker stats should not reset when rotateBucketController provided and no event emitted', t => { + t.plan(1); + + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter, + rollingCountTimeout: 10 + }); + + breaker.fire(-1) + .catch(() => { + setTimeout(() => { + t.equal(breaker.status.stats.failures, 1, 'failures do not reset because no rotate event is emitted'); + breaker.shutdown(); + t.end(); + }, 100); + }); +}); + +test('CircuitBreaker status - breaker stats should rotate when rotateBucketController provided and "rotate" event emitted', t => { + t.plan(1); + + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter, + rollingCountBuckets: 1, + rollingCountTimeout: 10000000 + }); + + breaker.fire(-1) + .catch(() => { + emitter.emit('rotate'); + t.equal(breaker.status.stats.failures, 0, 'failures reset buckets are rotated by EventEmitter'); + breaker.shutdown(); + t.end(); + }); +}); + +test('CircuitBreaker status - breaker stats should reset when rotateBucketController not provided', t => { + t.plan(1); + + const breaker = new CircuitBreaker(passFail, { + rollingCountTimeout: 10 + }); + + breaker.fire(-1) + .catch(() => { + setTimeout(() => { + t.equal(breaker.status.stats.failures, 0, 'failures reset because no event emitter is provided and rollingCountTimeout set to 10ms'); + breaker.shutdown(); + t.end(); + }, 100); + }); +}); From c7814c4ae1b14a0304a5bf477bf0b3f9db472622 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Fri, 25 Aug 2023 12:36:37 -0700 Subject: [PATCH 04/17] feat: fix event emitter import --- test/status-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/status-test.js b/test/status-test.js index 1ffa5b65..93ceba86 100644 --- a/test/status-test.js +++ b/test/status-test.js @@ -1,6 +1,6 @@ 'use strict'; -const { EventEmitter } = require('stream'); +const { EventEmitter } = require('node:events'); const test = require('tape'); const CircuitBreaker = require('../'); const Status = require('../lib/status.js'); From 342b977d1a7b5d5cf1709206eb55d88df2006d2b Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Fri, 25 Aug 2023 12:41:18 -0700 Subject: [PATCH 05/17] feat: fix import --- test/status-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/status-test.js b/test/status-test.js index 93ceba86..9b5bc16a 100644 --- a/test/status-test.js +++ b/test/status-test.js @@ -1,6 +1,6 @@ 'use strict'; -const { EventEmitter } = require('node:events'); +const { EventEmitter } = require('events'); const test = require('tape'); const CircuitBreaker = require('../'); const Status = require('../lib/status.js'); From 0157369d9013cc17863f5624be38565a182a0370 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Mon, 11 Sep 2023 22:49:04 -0700 Subject: [PATCH 06/17] feat: remove listener on disable --- lib/circuit.js | 2 ++ lib/status.js | 25 +++++++++++++++++++++---- test/enable-disable-test.js | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/circuit.js b/lib/circuit.js index 524e40c4..c07d1531 100644 --- a/lib/circuit.js +++ b/lib/circuit.js @@ -799,6 +799,7 @@ class CircuitBreaker extends EventEmitter { */ enable () { this[ENABLED] = true; + this.status.startListeneningForRotateEvent(); } /** @@ -808,6 +809,7 @@ class CircuitBreaker extends EventEmitter { */ disable () { this[ENABLED] = false; + this.status.removeRotateBucketControllerListener(); } } diff --git a/lib/status.js b/lib/status.js index 0a29ee3d..86128b73 100644 --- a/lib/status.js +++ b/lib/status.js @@ -6,6 +6,7 @@ const TIMEOUT = Symbol('timeout'); const PERCENTILES = Symbol('percentiles'); const BUCKET_INTERVAL = Symbol('bucket-interval'); const SNAPSHOT_INTERVAL = Symbol('snapshot-interval'); +const ROTATE_EVENT_NAME = Symbol('rotate-event-name'); const EventEmitter = require('events').EventEmitter; @@ -56,6 +57,7 @@ class Status extends EventEmitter { this[TIMEOUT] = options.rollingCountTimeout || 10000; this[WINDOW] = new Array(this[BUCKETS]); this[PERCENTILES] = [0.0, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.995, 1]; + this[ROTATE_EVENT_NAME] = 'rotate'; // Default this value to true this.rollingPercentilesEnabled = @@ -73,12 +75,10 @@ class Status extends EventEmitter { const bucketInterval = Math.floor(this[TIMEOUT] / this[BUCKETS]); if (this.rotateBucketController) { - // rotate the buckets based on an optional EventEmitter - this.rotateBucketController.on('rotate', nextBucket(this[WINDOW])); + this.startListeneningForRotateEvent(); } else { // or rotate the buckets periodically - this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), - bucketInterval); + this[BUCKET_INTERVAL] = setInterval(() => this.rotateBucket(this[WINDOW]), bucketInterval); // No unref() in the browser if (typeof this[BUCKET_INTERVAL].unref === 'function') { this[BUCKET_INTERVAL].unref(); @@ -189,6 +189,23 @@ class Status extends EventEmitter { clearInterval(this[SNAPSHOT_INTERVAL]); } } + + rotateBucket (window) { + nextBucket(window); + } + + removeRotateBucketControllerListener () { + if (this.rotateBucketController) { + this.rotateBucketController.removeListener(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); + } + } + + startListeneningForRotateEvent () { + if (this.rotateBucketController) { + // this.rotateBucketController.on(this[ROTATE_EVENT_NAME], () => this.rotateBucket(this[WINDOW])); + this.rotateBucketController.on(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); + } + } } const nextBucket = window => _ => { diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index 132c4750..d76c178d 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -51,7 +51,7 @@ test('When disabled the circuit should always be closed', t => { breaker.fire(-1) .catch(e => t.equals(e, 'Error: -1 is < 0')) .then(() => { - t.ok(breaker.opened, 'should be closed'); + t.ok(breaker.opened, 'should be open'); }) .then(_ => breaker.shutdown()) .then(t.end); From 9f41fe9071ea303bb7082a1d756ddb8123774a46 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 00:19:19 -0700 Subject: [PATCH 07/17] feat: fix nextBucket call in periodic rotations --- lib/status.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/status.js b/lib/status.js index 86128b73..167c311a 100644 --- a/lib/status.js +++ b/lib/status.js @@ -75,10 +75,11 @@ class Status extends EventEmitter { const bucketInterval = Math.floor(this[TIMEOUT] / this[BUCKETS]); if (this.rotateBucketController) { + // rotate the buckets based on an optional EventEmitter this.startListeneningForRotateEvent(); } else { // or rotate the buckets periodically - this[BUCKET_INTERVAL] = setInterval(() => this.rotateBucket(this[WINDOW]), bucketInterval); + this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), bucketInterval); // No unref() in the browser if (typeof this[BUCKET_INTERVAL].unref === 'function') { this[BUCKET_INTERVAL].unref(); @@ -190,10 +191,6 @@ class Status extends EventEmitter { } } - rotateBucket (window) { - nextBucket(window); - } - removeRotateBucketControllerListener () { if (this.rotateBucketController) { this.rotateBucketController.removeListener(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); @@ -202,7 +199,6 @@ class Status extends EventEmitter { startListeneningForRotateEvent () { if (this.rotateBucketController) { - // this.rotateBucketController.on(this[ROTATE_EVENT_NAME], () => this.rotateBucket(this[WINDOW])); this.rotateBucketController.on(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); } } From a92d305edeee308f0571d4090ba2950b486088cf Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 01:06:18 -0700 Subject: [PATCH 08/17] feat: resolve warnings --- lib/status.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/status.js b/lib/status.js index 167c311a..82987bf6 100644 --- a/lib/status.js +++ b/lib/status.js @@ -79,7 +79,8 @@ class Status extends EventEmitter { this.startListeneningForRotateEvent(); } else { // or rotate the buckets periodically - this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), bucketInterval); + this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), + bucketInterval); // No unref() in the browser if (typeof this[BUCKET_INTERVAL].unref === 'function') { this[BUCKET_INTERVAL].unref(); @@ -193,13 +194,15 @@ class Status extends EventEmitter { removeRotateBucketControllerListener () { if (this.rotateBucketController) { - this.rotateBucketController.removeListener(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); + this.rotateBucketController.removeListener(this[ROTATE_EVENT_NAME], + nextBucket(this[WINDOW])); } } startListeneningForRotateEvent () { if (this.rotateBucketController) { - this.rotateBucketController.on(this[ROTATE_EVENT_NAME], nextBucket(this[WINDOW])); + this.rotateBucketController.on(this[ROTATE_EVENT_NAME], + nextBucket(this[WINDOW])); } } } From 13860e9dc41eed57c845dfce6aadf1b01a1653a5 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 01:49:18 -0700 Subject: [PATCH 09/17] feat: fix lint --- lib/status.js | 8 +++---- test/enable-disable-test.js | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/status.js b/lib/status.js index 82987bf6..8ebb982a 100644 --- a/lib/status.js +++ b/lib/status.js @@ -68,6 +68,7 @@ class Status extends EventEmitter { // can be undefined this.rotateBucketController = options.rotateBucketController; + this.rotateBucket = nextBucket(this[WINDOW]); // prime the window with buckets for (let i = 0; i < this[BUCKETS]; i++) this[WINDOW][i] = bucket(); @@ -79,8 +80,7 @@ class Status extends EventEmitter { this.startListeneningForRotateEvent(); } else { // or rotate the buckets periodically - this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), - bucketInterval); + this[BUCKET_INTERVAL] = setInterval(this.rotateBucket, bucketInterval); // No unref() in the browser if (typeof this[BUCKET_INTERVAL].unref === 'function') { this[BUCKET_INTERVAL].unref(); @@ -195,14 +195,14 @@ class Status extends EventEmitter { removeRotateBucketControllerListener () { if (this.rotateBucketController) { this.rotateBucketController.removeListener(this[ROTATE_EVENT_NAME], - nextBucket(this[WINDOW])); + this.rotateBucket); } } startListeneningForRotateEvent () { if (this.rotateBucketController) { this.rotateBucketController.on(this[ROTATE_EVENT_NAME], - nextBucket(this[WINDOW])); + this.rotateBucket); } } } diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index d76c178d..cbae061f 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -3,6 +3,7 @@ const test = require('tape'); const CircuitBreaker = require('../'); const { passFail } = require('./common'); +const EventEmitter = require('events').EventEmitter; test('Defaults to enabled', t => { t.plan(1); @@ -57,3 +58,45 @@ test('When disabled the circuit should always be closed', t => { .then(t.end); }); }); + +test('When disabled, the event emitter listener should be removed', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.disable(); + t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); + breaker.shutdown(); + t.end(); +}); + +test('Event listener should be removed only for the breaker that is disabled', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breakerToBeDisabled = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + const breakerNotToBeDisabled = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 2, '1 listener attached for each breaker'); + breakerToBeDisabled.disable(); + t.equals(emitter.listeners('rotate').length, 1, '1 listener should be disabled and 1 should remain'); + t.end(); +}); + +test.only('Event listener should be re-added when circuit is re-enabled', t => { + t.plan(3); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.disable(); + t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 1, 'listener re-attached when breaker re-enabled'); + t.end(); +}); From 6810a868164a2be49c86264d3f1a7bcc85fc5076 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 01:51:28 -0700 Subject: [PATCH 10/17] feat: unused variable --- test/enable-disable-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index cbae061f..5d8cdc6e 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -78,7 +78,7 @@ test('Event listener should be removed only for the breaker that is disabled', t const breakerToBeDisabled = new CircuitBreaker(passFail, { rotateBucketController: emitter }); - const breakerNotToBeDisabled = new CircuitBreaker(passFail, { + const _breakerNotToBeDisabled = new CircuitBreaker(passFail, { rotateBucketController: emitter }); t.equals(emitter.listeners('rotate').length, 2, '1 listener attached for each breaker'); From 1fa1a276bd7017e2afdcc27dc7e53da049e7d09c Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 01:51:41 -0700 Subject: [PATCH 11/17] feat: remove only --- test/enable-disable-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index 5d8cdc6e..654cc447 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -87,7 +87,7 @@ test('Event listener should be removed only for the breaker that is disabled', t t.end(); }); -test.only('Event listener should be re-added when circuit is re-enabled', t => { +test('Event listener should be re-added when circuit is re-enabled', t => { t.plan(3); const emitter = new EventEmitter(); const breaker = new CircuitBreaker(passFail, { From e2cb181f8486925105981d448a1a4c1e7d310965 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 02:02:17 -0700 Subject: [PATCH 12/17] feat: add semicolon --- lib/status.js | 4 +++- test/enable-disable-test.js | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/status.js b/lib/status.js index 8ebb982a..684f0b16 100644 --- a/lib/status.js +++ b/lib/status.js @@ -186,6 +186,8 @@ class Status extends EventEmitter { // interval is not set if rotateBucketController is provided if (this.rotateBucketController === undefined) { clearInterval(this[BUCKET_INTERVAL]); + } else { + this.removeRotateBucketControllerListener(); } if (this.enableSnapshots) { clearInterval(this[SNAPSHOT_INTERVAL]); @@ -200,7 +202,7 @@ class Status extends EventEmitter { } startListeneningForRotateEvent () { - if (this.rotateBucketController) { + if (this.rotateBucketController && this.rotateBucketController.listenerCount('rotate', this.rotateBucket) === 0) { this.rotateBucketController.on(this[ROTATE_EVENT_NAME], this.rotateBucket); } diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index 654cc447..55aabd38 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -78,12 +78,14 @@ test('Event listener should be removed only for the breaker that is disabled', t const breakerToBeDisabled = new CircuitBreaker(passFail, { rotateBucketController: emitter }); - const _breakerNotToBeDisabled = new CircuitBreaker(passFail, { + const breakerNotToBeDisabled = new CircuitBreaker(passFail, { rotateBucketController: emitter }); t.equals(emitter.listeners('rotate').length, 2, '1 listener attached for each breaker'); breakerToBeDisabled.disable(); t.equals(emitter.listeners('rotate').length, 1, '1 listener should be disabled and 1 should remain'); + breakerToBeDisabled.shutdown(); + breakerNotToBeDisabled.shutdown(); t.end(); }); @@ -98,5 +100,36 @@ test('Event listener should be re-added when circuit is re-enabled', t => { t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); breaker.enable(); t.equals(emitter.listeners('rotate').length, 1, 'listener re-attached when breaker re-enabled'); + breaker.shutdown(); + t.end(); +}); + +test('Listener should not be attached with a call to enable if there is already a listener', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 1, 'listener should not be added again'); + breaker.shutdown(); + t.end(); +}); + +test('Listener should not be attached with a call to enable if there is already a listener and there is another breaker in the mix', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + const anotherBreaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 2, 'listener attached automatically'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 2, 'listener should not be added again'); + breaker.shutdown(); + anotherBreaker.shutdown(); t.end(); }); From 627729d9cc8f3d5fb119f80756f90ba33cbe6a53 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 02:04:25 -0700 Subject: [PATCH 13/17] feat: use constant --- lib/status.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/status.js b/lib/status.js index 684f0b16..53a05fcc 100644 --- a/lib/status.js +++ b/lib/status.js @@ -202,7 +202,11 @@ class Status extends EventEmitter { } startListeneningForRotateEvent () { - if (this.rotateBucketController && this.rotateBucketController.listenerCount('rotate', this.rotateBucket) === 0) { + if ( + this.rotateBucketController && + this.rotateBucketController.listenerCount(this[ROTATE_EVENT_NAME], + this.rotateBucket) === 0 + ) { this.rotateBucketController.on(this[ROTATE_EVENT_NAME], this.rotateBucket); } From a3966be674cf4ef6e33def8d694812817794c01f Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 02:08:35 -0700 Subject: [PATCH 14/17] feat: add space --- lib/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/status.js b/lib/status.js index 53a05fcc..08cf125b 100644 --- a/lib/status.js +++ b/lib/status.js @@ -240,4 +240,4 @@ function calculatePercentile (percentile, arr) { return arr[idx - 1] || 0; } -module.exports = exports = Status; +module.exports = exports = Status; \ No newline at end of file From f4e139d78c1bde26093b603b6bd990478f94e0f9 Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Tue, 12 Sep 2023 02:10:07 -0700 Subject: [PATCH 15/17] feat: add new line --- lib/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/status.js b/lib/status.js index 08cf125b..53a05fcc 100644 --- a/lib/status.js +++ b/lib/status.js @@ -240,4 +240,4 @@ function calculatePercentile (percentile, arr) { return arr[idx - 1] || 0; } -module.exports = exports = Status; \ No newline at end of file +module.exports = exports = Status; From 96b0d77aa66e383cbd1f4c39adde333b9fe17218 Mon Sep 17 00:00:00 2001 From: Lucas Holmquist Date: Tue, 12 Sep 2023 11:16:45 -0400 Subject: [PATCH 16/17] fix: exclude the rolling event emitter tests from the browser tests --- test/browser/generate-index.sh | 2 +- test/enable-disable-test.js | 75 --------------------------- test/rolling-event-emitter-test.js | 81 ++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 76 deletions(-) create mode 100644 test/rolling-event-emitter-test.js diff --git a/test/browser/generate-index.sh b/test/browser/generate-index.sh index 560b0080..e92861bc 100755 --- a/test/browser/generate-index.sh +++ b/test/browser/generate-index.sh @@ -3,7 +3,7 @@ echo $PWD cd test -file_list=$(ls -1 | grep .js) +file_list=$(ls -1 | grep .js | grep -v rolling-event-emitter) cd .. requires="" diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index 55aabd38..fa2fe744 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -58,78 +58,3 @@ test('When disabled the circuit should always be closed', t => { .then(t.end); }); }); - -test('When disabled, the event emitter listener should be removed', t => { - t.plan(2); - const emitter = new EventEmitter(); - const breaker = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); - breaker.disable(); - t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); - breaker.shutdown(); - t.end(); -}); - -test('Event listener should be removed only for the breaker that is disabled', t => { - t.plan(2); - const emitter = new EventEmitter(); - const breakerToBeDisabled = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - const breakerNotToBeDisabled = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - t.equals(emitter.listeners('rotate').length, 2, '1 listener attached for each breaker'); - breakerToBeDisabled.disable(); - t.equals(emitter.listeners('rotate').length, 1, '1 listener should be disabled and 1 should remain'); - breakerToBeDisabled.shutdown(); - breakerNotToBeDisabled.shutdown(); - t.end(); -}); - -test('Event listener should be re-added when circuit is re-enabled', t => { - t.plan(3); - const emitter = new EventEmitter(); - const breaker = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); - breaker.disable(); - t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); - breaker.enable(); - t.equals(emitter.listeners('rotate').length, 1, 'listener re-attached when breaker re-enabled'); - breaker.shutdown(); - t.end(); -}); - -test('Listener should not be attached with a call to enable if there is already a listener', t => { - t.plan(2); - const emitter = new EventEmitter(); - const breaker = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); - breaker.enable(); - t.equals(emitter.listeners('rotate').length, 1, 'listener should not be added again'); - breaker.shutdown(); - t.end(); -}); - -test('Listener should not be attached with a call to enable if there is already a listener and there is another breaker in the mix', t => { - t.plan(2); - const emitter = new EventEmitter(); - const breaker = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - const anotherBreaker = new CircuitBreaker(passFail, { - rotateBucketController: emitter - }); - t.equals(emitter.listeners('rotate').length, 2, 'listener attached automatically'); - breaker.enable(); - t.equals(emitter.listeners('rotate').length, 2, 'listener should not be added again'); - breaker.shutdown(); - anotherBreaker.shutdown(); - t.end(); -}); diff --git a/test/rolling-event-emitter-test.js b/test/rolling-event-emitter-test.js new file mode 100644 index 00000000..b928ec94 --- /dev/null +++ b/test/rolling-event-emitter-test.js @@ -0,0 +1,81 @@ +'use strict'; + +const test = require('tape'); +const CircuitBreaker = require('../'); +const { passFail } = require('./common'); +const EventEmitter = require('events').EventEmitter; + +test('When disabled, the event emitter listener should be removed', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.disable(); + t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); + breaker.shutdown(); + t.end(); +}); + +test('Event listener should be removed only for the breaker that is disabled', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breakerToBeDisabled = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + const breakerNotToBeDisabled = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 2, '1 listener attached for each breaker'); + breakerToBeDisabled.disable(); + t.equals(emitter.listeners('rotate').length, 1, '1 listener should be disabled and 1 should remain'); + breakerToBeDisabled.shutdown(); + breakerNotToBeDisabled.shutdown(); + t.end(); +}); + +test('Event listener should be re-added when circuit is re-enabled', t => { + t.plan(3); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.disable(); + t.equals(emitter.listeners('rotate').length, 0, 'listener removed when breaker disabled'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 1, 'listener re-attached when breaker re-enabled'); + breaker.shutdown(); + t.end(); +}); + +test('Listener should not be attached with a call to enable if there is already a listener', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 1, 'listener attached automatically'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 1, 'listener should not be added again'); + breaker.shutdown(); + t.end(); +}); + +test('Listener should not be attached with a call to enable if there is already a listener and there is another breaker in the mix', t => { + t.plan(2); + const emitter = new EventEmitter(); + const breaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + const anotherBreaker = new CircuitBreaker(passFail, { + rotateBucketController: emitter + }); + t.equals(emitter.listeners('rotate').length, 2, 'listener attached automatically'); + breaker.enable(); + t.equals(emitter.listeners('rotate').length, 2, 'listener should not be added again'); + breaker.shutdown(); + anotherBreaker.shutdown(); + t.end(); +}); From 1687143074f4cf8263f9b1fafb37eea0bbc7943d Mon Sep 17 00:00:00 2001 From: Gautam Jethwani Date: Wed, 13 Sep 2023 00:49:02 -0700 Subject: [PATCH 17/17] feat: remove unused EventEmitter --- test/enable-disable-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/enable-disable-test.js b/test/enable-disable-test.js index fa2fe744..d76c178d 100644 --- a/test/enable-disable-test.js +++ b/test/enable-disable-test.js @@ -3,7 +3,6 @@ const test = require('tape'); const CircuitBreaker = require('../'); const { passFail } = require('./common'); -const EventEmitter = require('events').EventEmitter; test('Defaults to enabled', t => { t.plan(1);