From 1cc6646fbabacf7693c4d6b0bd063e360a2730c6 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 9 May 2022 15:15:13 +0200 Subject: [PATCH 1/3] Added a second timeout(1) to force spinning again the event loop lap and verify if the initial operation has been successful --- docker-compose.yml | 6 +++ lib/cb.js | 45 ++++++++++++++++ lib/client.js | 2 +- package.json | 3 +- test/cb.tests.js | 124 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yml create mode 100644 lib/cb.js create mode 100644 test/cb.tests.js diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..74d8390 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +services: + redis: + image: 'redis:6' + command: --save "" --appendonly no + ports: + - "6379:6379" diff --git a/lib/cb.js b/lib/cb.js new file mode 100644 index 0000000..621ac06 --- /dev/null +++ b/lib/cb.js @@ -0,0 +1,45 @@ +// Based on the work of Jeremy Martin: https://github.com/jmar777/cb +// Added a second timeout(1) to force spinning again the event loop lap and verify if the initial operation has been successful +module.exports = function(callback) { + + var cb = function() { + if (timedout || (once && count)) return; + count += 1; + tid && clearTimeout(tid); + + var args = Array.prototype.slice.call(arguments); + process.nextTick(function() { + if (!errback) return callback.apply(this, args); + args[0] ? errback(args[0]) : callback.apply(this, args.slice(1)); + }); + + }, count = 0, once = false, timedout = false, errback, tid; + + cb.timeout = function(ms) { + tid && clearTimeout(tid); + tid = setTimeout(function() { + // force another second timeout to verify if the operation has been successful + // No need to clear timeout since it has been triggered + tid = setTimeout(function() { + cb(new TimeoutError(ms)); + timedout = true; + }, 1); + }, ms - 1); + return cb; + }; + + cb.error = function(func) { errback = func; return cb; }; + + cb.once = function() { once = true; return cb; }; + + return cb; + +}; + +var TimeoutError = module.exports.TimeoutError = function TimeoutError(ms) { + this.message = 'Specified timeout of ' + ms + 'ms was reached'; + Error.captureStackTrace(this, this.constructor); +}; +TimeoutError.prototype = new Error; +TimeoutError.prototype.constructor = TimeoutError; +TimeoutError.prototype.name = 'TimeoutError'; \ No newline at end of file diff --git a/lib/client.js b/lib/client.js index ce01e42..2bc5a9b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,6 +1,6 @@ const _ = require('lodash'); const retry = require('retry'); -const cbControl = require('cb'); +const cbControl = require('./cb'); const validation = require('./validation'); const LimitDBRedis = require('./db'); const disyuntor = require('disyuntor'); diff --git a/package.json b/package.json index 9470efb..75a5c7e 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,12 @@ "url": "http://github.com/auth0/limitd-redis.git" }, "scripts": { - "test": "NODE_ENV=test nyc mocha --exit" + "test": "trap 'docker-compose down --remove-orphans -v' EXIT; docker-compose up -d && NODE_ENV=test nyc mocha --exit" }, "author": "Auth0", "license": "MIT", "dependencies": { "async": "^2.6.1", - "cb": "^0.1.0", "disyuntor": "^3.5.0", "ioredis": "^4.5.1", "lodash": "^4.17.15", diff --git a/test/cb.tests.js b/test/cb.tests.js new file mode 100644 index 0000000..41b4a33 --- /dev/null +++ b/test/cb.tests.js @@ -0,0 +1,124 @@ +var assert = require('assert'), + cb = require('../lib/cb'); + +function invokeAsync(callback) { + setTimeout(function() { + callback(null, 'foo'); + }, 100); +} + +function invokeAsyncError(callback) { + setTimeout(function() { + callback(new Error()); + }, 100); +} + +function invokeAsyncTwice(callback) { + setTimeout(function() { + callback(null, 'foo'); + callback(null, 'foo'); + }, 100); +} + +describe('cb(callback)', function() { + + it('should invoke the provided callback', function(done) { + invokeAsync(cb(function(err, res) { + assert.strictEqual(res, 'foo'); + done(); + })); + }); + + it('shouldn\'t mess with errors', function(done) { + invokeAsyncError(cb(function(err, res) { + assert(err); + done(); + })); + }); + + it('should allow multiple executions', function(done) { + var count = 0; + invokeAsyncTwice(cb(function(err, res) { + count++; + if (count === 2) done(); + })); + }); + +}); + +describe('cb(callback).timeout(ms)', function() { + + it('should complete successfully within timeout period', function(done) { + invokeAsync(cb(function(err, res) { + assert.strictEqual(res, 'foo'); + done(); + }).timeout(200)); + }); + + it('should complete with an error after timeout period', function(done) { + invokeAsync(cb(function(err, res) { + assert(err); + done(); + }).timeout(50)); + }); + + it('error resulting from a timeout should be instanceof cb.TimeoutError', function(done) { + invokeAsync(cb(function(err, res) { + assert(err instanceof cb.TimeoutError); + done(); + }).timeout(50)); + }); +}); + +describe('cb(callback).error(errback)', function() { + + it('should skip the err argument when invoking callback', function(done) { + invokeAsync(cb(function(res) { + assert.strictEqual(res, 'foo'); + done(); + }).error(assert.ifError)); + }); + + it('should pass errors to provided errback', function(done) { + invokeAsyncError(cb(function(res) { + throw new Error('should not be invoked'); + }).error(function(err) { + assert(err); + done(); + })); + }); + +}); + +describe('cb(callback).error(errback).timeout(ms)', function() { + + it('should skip the err argument when invoking callback', function(done) { + invokeAsync(cb(function(res) { + assert.strictEqual(res, 'foo'); + done(); + }).error(assert.ifError).timeout(200)); + }); + + it('should pass timeout error to provided errback', function(done) { + invokeAsyncError(cb(function(res) { + throw new Error('should not be invoked'); + }).error(function(err) { + assert(err); + done(); + }).timeout(50)); + }); + +}); + +describe('cb(callback).once()', function() { + + it('should allow multiple executions', function(done) { + var count = 0; + invokeAsyncTwice(cb(function(err, res) { + count++; + assert.notEqual(count, 2); + setTimeout(done, 100); + }).once()); + }); + +}); From 8f213253c540dff6f49f16f7ba323428bc53cadd Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 16 May 2022 14:35:12 +0200 Subject: [PATCH 2/3] increase patch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75a5c7e..1936e66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "limitd-redis", - "version": "7.1.0", + "version": "7.1.1", "description": "A database client for limits on top of redis", "main": "index.js", "repository": { From e579f95c1537a5170acd2eead92c9c4517c8d860 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 3 Jun 2022 11:45:12 +0200 Subject: [PATCH 3/3] bump minor version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1936e66..e40fe3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "limitd-redis", - "version": "7.1.1", + "version": "7.2.0", "description": "A database client for limits on top of redis", "main": "index.js", "repository": {