Skip to content

Commit

Permalink
test: add semaphore rate limiting test
Browse files Browse the repository at this point in the history
Also removed two of the existing semaphore tests that were not really
automated tests, but rather some debugging information about how the
semaphore rate limiting works.
  • Loading branch information
lance committed Jun 19, 2017
1 parent 3d990c3 commit b3d7b6f
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 83 deletions.
2 changes: 1 addition & 1 deletion lib/circuit.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CircuitBreaker extends EventEmitter {
this.options.rollingCountTimeout = options.rollingCountTimeout || 10000;
this.options.rollingCountBuckets = options.rollingCountBuckets || 10;
this.options.rollingPercentilesEnabled = options.rollingPercentilesEnabled !== false;
this.options.capacity = options.capacity || 10;
this.options.capacity = typeof options.capacity === 'number' ? options.capacity : 10;
this.semaphore = new Semaphore(this.options.capacity);

this[STATUS] = new Status(this.options);
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"jsdoc": "~3.4.2",
"nsp": "~2.6.2",
"opener": "~1.4.2",
"run-parallel": "^1.1.6",
"standard-version": "^4.0.0",
"szero": "^0.9.0",
"tap-spec": "~4.1.1",
Expand Down
98 changes: 17 additions & 81 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -719,89 +719,21 @@ test('Circuit Breaker timeout with semaphore released', (t) => {
breaker.fire(-1).catch(() => {});
});

test('Semaphore capacity limit', (t) => {
t.plan(4);
const breaker = cb('foobar', { capacity: 1 });

breaker.fire()
.then(() => t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`))
.catch(t.fail)
.then(() => {
breaker.fire()
.then(() => t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`))
.catch(t.fail);
});
breaker.fire()
.then(() => t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`))
.catch(t.fail)
.then(() => {
breaker.fire()
.then(() => t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`))
.then(t.end)
.catch(t.fail);
});
});

test('Semaphore capacity limit in parallel', (t) => {
const parallel = require('run-parallel');
t.plan(24);
const breaker = cb('foobar', { capacity: 1 });

const tasks = [
() => {
breaker.fire()
.then((result) => {
t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`);
t.ok(true, `failures: ${breaker.stats.failures}`);
t.ok(true, `fallbacks: ${breaker.stats.fallbacks}`);
t.ok(true, `successes: ${breaker.stats.successes}`);
t.ok(true, `rejects: ${breaker.stats.rejects}`);
t.ok(true, `fires: ${breaker.stats.fires}`);
})
.catch(t.fail);
},
() => {
breaker.fire()
.then((result) => {
t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`);
t.ok(true, `failures: ${breaker.stats.failures}`);
t.ok(true, `fallbacks: ${breaker.stats.fallbacks}`);
t.ok(true, `successes: ${breaker.stats.successes}`);
t.ok(true, `rejects: ${breaker.stats.rejects}`);
t.ok(true, `fires: ${breaker.stats.fires}`);
})
.catch(t.fail);
},
() => {
breaker.fire()
.then((result) => {
t.ok(true, `semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`);
t.ok(true, `failures: ${breaker.stats.failures}`);
t.ok(true, `fallbacks: ${breaker.stats.fallbacks}`);
t.ok(true, `successes: ${breaker.stats.successes}`);
t.ok(true, `rejects: ${breaker.stats.rejects}`);
t.ok(true, `fires: ${breaker.stats.fires}`);
})
.catch(t.fail);
}
];
test('CircuitBreaker semaphore rate limiting', (t) => {
t.plan(2);
let timedOut = false;
const breaker = cb(timedFunction, { timeout: 300, capacity: 1 });

parallel(tasks, (error) => {
t.error(error);
t.fail();
// fire once to acquire the semaphore and hold it for a long time
breaker.fire(1000).catch(e => {
t.equals(e.code, 'ETIMEDOUT', 'Breaker timed out');
timedOut = true;
});

breaker.fire()
.then((result) => {
t.ok(true, `This is not in parallel -> semaphore count is: ${breaker.semaphore.count} and initial capacity is: ${breaker.options.capacity}`);
t.ok(true, `failures: ${breaker.stats.failures}`);
t.ok(true, `fallbacks: ${breaker.stats.fallbacks}`);
t.ok(true, `successes: ${breaker.stats.successes}`);
t.ok(true, `rejects: ${breaker.stats.rejects}`);
t.ok(true, `fires: ${breaker.stats.fires}`);
})
.then(t.end)
.catch(t.fail);
breaker.fire(0).then(_ => {
t.ok(timedOut, 'Breaker delayed execution on semaphore acquisition');
t.end();
}).catch(t.fail);
});

/**
Expand All @@ -823,10 +755,14 @@ function passFail (x) {
* after 1 second.
*/
function slowFunction () {
return timedFunction(10000);
}

function timedFunction (ms) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve('done');
}, 10000);
}, ms);
if (typeof timer.unref === 'function') {
timer.unref();
}
Expand Down

0 comments on commit b3d7b6f

Please sign in to comment.