diff --git a/README.md b/README.md index b22abd3..cd9a5d1 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,37 @@ lock("myLock", function(done) { }); ``` +### Disabling retries +Retries are very useful if you want all operations requesting the locks to +eventually be executed and completed. + +But sometimes you only care about the first lock, and every subsequent attempt +to acquire the same lock should just be dropped. + +You can do this by setting retryDelay timeout to 0: + +```javascript +var client = require("redis").createClient(), + lock = require("redis-lock")(client, 0); // <-- disabling retries + +// since we disabled retries, a new parameter "error" is passed to the callback +lock("myLock", function(err, done) { + if(!err){ + // Simulate a 1 second long operation + setTimeout(done, 1000); + } +}); + +// this lock will fail +lock("myLock", function(err, done) { + if(err && err.code == "ALREADY_LOCKED"){ + // already locked! + }else{ + done() + } +}); +``` + ## Installation $ npm install redis-lock diff --git a/index.js b/index.js index 7558e48..5b5752e 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ var util = require('util'); var defaultTimeout = 5000; var promisify = util.promisify || function(x) { return x; }; -function acquireLock(client, lockName, timeout, retryDelay, onLockAcquired) { +function acquireLock(client, lockName, timeout, retryDelay, onLockAcquired, onAlreadyLocked) { function retry() { setTimeout(function() { acquireLock(client, lockName, timeout, retryDelay, onLockAcquired); @@ -13,7 +13,10 @@ function acquireLock(client, lockName, timeout, retryDelay, onLockAcquired) { var lockTimeoutValue = (Date.now() + timeout + 1); client.set(lockName, lockTimeoutValue, 'PX', timeout, 'NX', function(err, result) { - if(err || result === null) return retry(); + if(err || result === null){ + if(retryDelay) return retry(); + else return onAlreadyLocked() + } onLockAcquired(lockTimeoutValue); }); } @@ -23,7 +26,7 @@ module.exports = function(client, retryDelay) { throw new Error("You must specify a client instance of http://github.com/mranney/node_redis"); } - retryDelay = retryDelay || 50; + retryDelay = typeof retryDelay === 'undefined' ? 50 : retryDelay; function lock(lockName, timeout, taskToPerform) { if(!lockName) { @@ -38,7 +41,7 @@ module.exports = function(client, retryDelay) { lockName = "lock." + lockName; acquireLock(client, lockName, timeout, retryDelay, function(lockTimeoutValue) { - taskToPerform(promisify(function(done) { + const doneCb = promisify(function(done) { done = done || function() {}; if(lockTimeoutValue > Date.now()) { @@ -46,7 +49,17 @@ module.exports = function(client, retryDelay) { } else { done(); } - })); + }) + + if(retryDelay){ + taskToPerform(doneCb); + }else{ + taskToPerform(null, doneCb); + } + }, function(){ + const err = new Error("Locked already acquired") + err.code = "ALREADY_LOCKED" + taskToPerform(err) // errored acquiring lock }); } diff --git a/test/test.js b/test/test.js index cfe4527..500efaf 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,7 @@ var should = require("should"), redisClient = require("redis").createClient(), - lock = require("../index")(redisClient) + lock = require("../index")(redisClient), + lockWithoutDelay = require("../index")(redisClient, 0), util = require("util"); const delay = ms => new Promise(res => setTimeout(res, ms)); @@ -53,6 +54,35 @@ describe("redis-lock", function() { } }); + + it("should throw error in second operation if delay is zero and first has lock", function(done) { + var errored = 0, success = 0; + lockWithoutDelay("testLock", function(err, completed) { + setTimeout(function() { + if(!err){ + success++; + completed(); + proceed(); + } + }, 500); // Longer, started first + }); + + lockWithoutDelay("testLock", function(err, completed) { + if(err && err.code == "ALREADY_LOCKED"){ + errored++ + } + proceed(); + }); + + function proceed() { + if(errored === 1 && success == 1) { + errored.should.equal(1); + done(); + } + } + }); + + it("shouldn't create a deadlock if the first operation doesn't release the lock within ", function(done) { var start = new Date(); lock("testLock", 300, function(completed) {