Skip to content

Commit

Permalink
Merge pull request #34 from auth0/fix_spinning_again_event_loop_lap
Browse files Browse the repository at this point in the history
fix: Added a second timeout(1) to force spinning again the event loop lap …
  • Loading branch information
javiersigler authored Jun 3, 2022
2 parents 2a25ea0 + e579f95 commit 6f06372
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 4 deletions.
6 changes: 6 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
redis:
image: 'redis:6'
command: --save "" --appendonly no
ports:
- "6379:6379"
45 changes: 45 additions & 0 deletions lib/cb.js
Original file line number Diff line number Diff line change
@@ -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';
2 changes: 1 addition & 1 deletion lib/client.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
{
"name": "limitd-redis",
"version": "7.1.0",
"version": "7.2.0",
"description": "A database client for limits on top of redis",
"main": "index.js",
"repository": {
"type": "git",
"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",
Expand Down
124 changes: 124 additions & 0 deletions test/cb.tests.js
Original file line number Diff line number Diff line change
@@ -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());
});

});

0 comments on commit 6f06372

Please sign in to comment.