Comparing version 1.0.1 to 2.0.0
{ | ||
"name": "redlock", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "A node.js redlock implementation for distributed redis locks", | ||
@@ -5,0 +5,0 @@ "main": "redlock.js", |
@@ -93,3 +93,8 @@ [![npm version](https://badge.fury.io/js/redlock.svg)](https://www.npmjs.com/package/redlock) | ||
// unlock your resource when you are done | ||
return lock.unlock(); | ||
return lock.unlock() | ||
.catch(function(err) { | ||
// we weren't able to reach redis; your lock will eventually | ||
// expire, but you probably want to log this error | ||
console.error(err); | ||
}); | ||
}); | ||
@@ -114,3 +119,8 @@ | ||
// unlock your resource when you are done | ||
return lock.unlock(); | ||
return lock.unlock() | ||
.catch(function(err) { | ||
// we weren't able to reach redis; your lock will eventually | ||
// expire, but you probably want to log this error | ||
console.error(err); | ||
}); | ||
}); | ||
@@ -139,4 +149,12 @@ }); | ||
using(redlock.disposer(resource, ttl), function(lock) { | ||
// if we weren't able to reach redis, your lock will eventually | ||
// expire, but you probably want to do something like log that | ||
// an error occurred; if you don't pass a handler, this error | ||
// will be ignored | ||
function unlockErrorHandler(err) { | ||
console.error(err); | ||
} | ||
using(redlock.disposer(resource, ttl, unlockErrorHandler), function(lock) { | ||
// ...do something here... | ||
@@ -152,3 +170,3 @@ | ||
```js | ||
using(redlock.disposer('locks:account:322456', 1000), function(lock) { | ||
using(redlock.disposer('locks:account:322456', 1000, unlockErrorHandler), function(lock) { | ||
@@ -204,3 +222,7 @@ // ...do something here... | ||
// unlock your resource when you are done | ||
lock.unlock(); | ||
lock.unlock(function(err) { | ||
// we weren't able to reach redis; your lock will eventually | ||
// expire, but you probably want to log this error | ||
console.error(err); | ||
}); | ||
} | ||
@@ -253,3 +275,3 @@ }); | ||
###`Redlock.lock(resource, ttl, callback)` | ||
###`Redlock.lock(resource, ttl, ?callback)` | ||
- `resource (string)` resource to be locked | ||
@@ -262,8 +284,9 @@ - `ttl (number)` time in ms until the lock expires | ||
###`Redlock.unlock(lock, callback)` | ||
###`Redlock.unlock(lock, ?callback)` | ||
- `lock (Lock)` lock to be released | ||
- `callback (function)` callback with no returning arguments | ||
- `callback (function)` callback returning: | ||
- `err (Error)` | ||
###`Redlock.extend(lock, ttl, callback)` | ||
###`Redlock.extend(lock, ttl, ?callback)` | ||
- `lock (Lock)` lock to be extended | ||
@@ -276,7 +299,15 @@ - `ttl (number)` time in ms to extend the lock's expiration | ||
###`Lock.unlock(callback)` | ||
- `callback (function)` callback with no returning arguments | ||
###`Redlock.disposer(resource, ttl, ?unlockErrorHandler)` | ||
- `resource (string)` resource to be locked | ||
- `ttl (number)` time in ms to extend the lock's expiration | ||
- `callback (function)` error handler called with: | ||
- `err (Error)` | ||
###`Lock.extend(ttl, callback)` | ||
###`Lock.unlock(?callback)` | ||
- `callback (function)` callback returning: | ||
- `err (Error)` | ||
###`Lock.extend(ttl, ?callback)` | ||
- `ttl (number)` time in ms to extend the lock's expiration | ||
@@ -283,0 +314,0 @@ - `callback (function)` callback returning: |
@@ -130,4 +130,7 @@ 'use strict'; | ||
// ``` | ||
Redlock.prototype.disposer = function disposer(resource, ttl) { | ||
return this._lock(resource, null, ttl).disposer(function(lock){ return lock.unlock(); }); | ||
Redlock.prototype.disposer = function disposer(resource, ttl, errorHandler) { | ||
errorHandler = errorHandler || function(err) {}; | ||
return this._lock(resource, null, ttl).disposer(function(lock){ | ||
return lock.unlock().catch(errorHandler); | ||
}); | ||
}; | ||
@@ -138,4 +141,6 @@ | ||
// ------ | ||
// This method unlocks the provided lock from all servers still persisting it. This is a | ||
// best-effort attempt and as such fails silently. | ||
// This method unlocks the provided lock from all servers still persisting it. It will fail | ||
// with an error if it is unable to release the lock on a quorum of nodes, but will make no | ||
// attempt to restore the lock on nodes that failed to release. It is safe to re-attempt an | ||
// unlock or to ignore the error, as the lock will automatically expire after its timeout. | ||
Redlock.prototype.release = | ||
@@ -146,10 +151,11 @@ Redlock.prototype.unlock = function unlock(lock, callback) { | ||
// the lock has expired | ||
if(lock.expiration < Date.now()) { | ||
return resolve(); | ||
} | ||
// invalidate the lock | ||
lock.expiration = 0; | ||
// the number of servers which have agreed to release this lock | ||
var votes = 0; | ||
// the number of votes needed for consensus | ||
var quorum = Math.floor(self.servers.length / 2) + 1; | ||
// the number of async redis calls still waiting to finish | ||
@@ -165,4 +171,19 @@ var waiting = self.servers.length; | ||
if(err) self.emit('clientError', err); | ||
// - if the lock was released by this call, it will return 1 | ||
// - if the lock has already been released, it will return 0 | ||
// - it may have been re-acquired by another process | ||
// - it may hava already been manually released | ||
// - it may have expired | ||
if(typeof response === 'number' && (response === 0 || response === 1)) | ||
votes++; | ||
if(waiting-- > 1) return; | ||
return resolve(); | ||
// SUCCESS: there is concensus and the lock is released | ||
if(votes >= quorum) | ||
return resolve(); | ||
// FAILURE: the lock could not be released | ||
return reject(new LockError('Unable to fully release the lock on resource "' + lock.resource + '".')); | ||
} | ||
@@ -169,0 +190,0 @@ }) |
54
test.js
@@ -40,8 +40,13 @@ 'use strict'; | ||
it('emits a clientError event when a client error occurs', function(done){ | ||
redlock.once('clientError', function(err) { | ||
var emitted = 0; | ||
function test(err) { | ||
assert.isNotNull(err); | ||
done(); | ||
}); | ||
emitted++; | ||
} | ||
redlock.on('clientError', test); | ||
redlock.lock(error, 200, function(err, lock){ | ||
redlock.removeListener('clientError', test); | ||
assert.isNotNull(err); | ||
assert.equal(emitted, 3); | ||
done(); | ||
}); | ||
@@ -90,3 +95,3 @@ }); | ||
it('should silently fail to unlock an already-unlocked resource', function(done) { | ||
it('should unlock an already-unlocked resource', function(done) { | ||
assert(two, 'Could not run because a required previous test failed.'); | ||
@@ -96,2 +101,12 @@ two.unlock(done); | ||
it('should error when unable to fully release a resource', function(done) { | ||
assert(two, 'Could not run because a required previous test failed.'); | ||
var failingTwo = Object.create(two); | ||
failingTwo.resource = error; | ||
failingTwo.unlock(function(err) { | ||
assert.isNotNull(err); | ||
done(); | ||
}); | ||
}); | ||
it('should fail to extend a lock on an already-unlocked resource', function(done) { | ||
@@ -212,3 +227,3 @@ assert(two, 'Could not run because a required previous test failed.'); | ||
it('should silently fail to unlock an already-unlocked resource', function(done) { | ||
it('should unlock an already-unlocked resource', function(done) { | ||
assert(two, 'Could not run because a required previous test failed.'); | ||
@@ -218,2 +233,12 @@ two.unlock().done(done, done); | ||
it('should error when unable to fully release a resource', function(done) { | ||
assert(two, 'Could not run because a required previous test failed.'); | ||
var failingTwo = Object.create(two); | ||
failingTwo.resource = error; | ||
failingTwo.unlock().done(done, function(err) { | ||
assert.isNotNull(err); | ||
done(); | ||
}); | ||
}); | ||
it('should fail to extend a lock on an already-unlocked resource', function(done) { | ||
@@ -329,2 +354,21 @@ assert(two, 'Could not run because a required previous test failed.'); | ||
it('should call unlockErrorHandler when unable to fully release a resource', function(done) { | ||
assert(two, 'Could not run because a required previous test failed.'); | ||
var errs = 0; | ||
var lock; | ||
Promise.using( | ||
redlock.disposer(resource, 800, function(err) { | ||
errs++; | ||
}), | ||
function(l){ | ||
lock = l; | ||
lock.resource = error; | ||
} | ||
).done(function() { | ||
assert.equal(errs, 1); | ||
lock.resource = resource; | ||
lock.unlock().done(done, done); | ||
}, done); | ||
}); | ||
var three_original, three_extended; | ||
@@ -331,0 +375,0 @@ var three_original_expiration; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32118
625
310