Skip to content

Commit

Permalink
Support promises, fix #3
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-marcacci committed Jul 21, 2015
1 parent 56812eb commit 46116a9
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pids
*.pid
*.seed

# Redis database
*.rdb

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

Expand Down
101 changes: 99 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,105 @@ var redlock = new Redlock(
```


Usage
-----
Usage (promise style)
---------------------


###Locking & Unocking

```js

// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';

// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;

redlock.lock(resource, ttl).then(function(lock) {

// ...do something here...

// unlock your resource when you are done
return lock.unlock();
});

```


###Locking and Extending

```js
redlock.lock('locks:account:322456', 1000).then(function(lock) {

// ...do something here...

// if you need more time, you can continue to extend
// the lock as long as you never let it expire
return lock.extend(1000).then(function(lock){

// ...do something here...

// unlock your resource when you are done
return lock.unlock();
});
});

```


Usage (disposer style)
----------------------


###Locking & Unocking

```js
var using = require('bluebird').using;

// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';

// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;

using(redlock.disposer(resource, ttl), function(lock) {

// ...do something here...

}); // <-- unlock is automatically handled by bluebird

```


###Locking and Extending

```js
using(redlock.disposer('locks:account:322456', 1000), function(lock) {

// ...do something here...

// if you need more time, you can continue to extend
// the lock until it expires
return lock.extend(1000).then(function(extended){

// Note that redlock modifies the original lock,
// so the vars `lock` and `extended` point to the
// exact same object

// ...do something here...

});
}); // <-- unlock is automatically handled by bluebird

```


Usage (callback style)
----------------------


###Locking & Unocking
Expand Down
44 changes: 41 additions & 3 deletions redlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,37 @@ function Redlock(clients, options) {
// }
// )
// ```
Redlock.prototype.acquire =
Redlock.prototype.lock = function lock(resource, ttl, callback) {
return this._lock(resource, null, ttl, callback);
};

// lock
// ----
// This method locks a resource using the redlock algorithm,
// and returns a bluebird disposer.
//
// ```js
// using(
// redlock.disposer(
// 'some-resource', // the resource to lock
// 2000 // ttl in ms
// ),
// function(lock) {
// ...
// }
// );
// ```
Redlock.prototype.disposer = function disposer(resource, ttl) {
return this._lock(resource, null, ttl).disposer(function(lock){ return lock.unlock(); });
};


// unlock
// ------
// This method unlocks the provided lock from all servers still persisting it. This is a
// best-effort attempt and as such fails silently.
Redlock.prototype.release =
Redlock.prototype.unlock = function unlock(lock, callback) {
var self = this;
return new Promise(function(resolve, reject) {
Expand All @@ -122,7 +144,10 @@ Redlock.prototype.unlock = function unlock(lock, callback) {
if(waiting-- > 1) return;
return resolve();
}
}).nodeify(callback);
})

// optionally run callback
.nodeify(callback);
};


Expand All @@ -137,7 +162,17 @@ Redlock.prototype.extend = function extend(lock, ttl, callback) {
return Promise.reject(new LockError('Cannot extend lock on resource "' + lock.resource + '" because the lock has already expired.')).nodeify(callback);

// extend the lock
return self._lock(lock.resource, lock.value, ttl, callback);
return self._lock(lock.resource, lock.value, ttl)

// modify and return the original lock object
.then(function(extension){
lock.value = extension.value;
lock.expiration = extension.expiration;
return lock;
})

// optionally run callback
.nodeify(callback);
};


Expand Down Expand Up @@ -241,7 +276,10 @@ Redlock.prototype._lock = function _lock(resource, value, ttl, callback) {
}

return attempt();
}).nodeify(callback);
})

// optionally run callback
.nodeify(callback);
};


Expand Down
84 changes: 84 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var assert = require('chai').assert;
var Promise = require('bluebird');
var Redlock = require('./redlock');

test('https://www.npmjs.com/package/redis', [require('redis').createClient()]);
Expand Down Expand Up @@ -100,6 +101,7 @@ function test(name, clients){
assert.isObject(lock);
assert.isAbove(lock.expiration, Date.now()-1);
assert.isAbove(lock.expiration, three.expiration-1);
assert.equal(three, lock);
four = lock;
done();
});
Expand Down Expand Up @@ -220,6 +222,7 @@ function test(name, clients){
assert.isObject(lock);
assert.isAbove(lock.expiration, Date.now()-1);
assert.isAbove(lock.expiration, three.expiration-1);
assert.equal(three, lock);
four = lock;
done();
}, done);
Expand Down Expand Up @@ -258,5 +261,86 @@ function test(name, clients){
});
});

describe('disposer', function(){
before(function(done){
var err;
var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
for (var i = clients.length - 1; i >= 0; i--) {
clients[i].del(resource, cb);
}
});

var one;
var one_expiration;
it('should automatically release a lock after the using block', function(done){
Promise.using(
redlock.disposer(resource, 200),
function(lock){
assert.isObject(lock);
assert.isAbove(lock.expiration, Date.now()-1);
one = lock;
one_expiration = lock.expiration;
}
).done(done, done);
});

var two;
var two_expiration;
it('should issue another lock immediately after a resource is unlocked', function(done){
assert(one_expiration, 'Could not run because a required previous test failed.');
Promise.using(
redlock.disposer(resource, 800),
function(lock){
assert.isObject(lock);
assert.isAbove(lock.expiration, Date.now()-1);
assert.isBelow(Date.now()-1, one_expiration);
two = lock;
two_expiration = lock.expiration;
}
).done(done, done);
});

var three_original, three_extended;
var three_original_expiration;
var three_extended_expiration;
it('should automatically release an extended lock', function(done){
assert(two_expiration, 'Could not run because a required previous test failed.');
Promise.using(
redlock.disposer(resource, 200),
function(lock){
assert.isObject(lock);
assert.isAbove(lock.expiration, Date.now()-1);
assert.isBelow(Date.now()-1, two_expiration);
three_original = lock;
three_original_expiration = lock.expiration;

return Promise.delay(100)
.then(function(){ return lock.extend(200); })
.then(function(extended) {
assert.isObject(extended);
assert.isAbove(extended.expiration, Date.now()-1);
assert.isBelow(Date.now()-1, three_original_expiration);
assert.isAbove(extended.expiration, three_original_expiration);
assert.equal(extended, lock);
three_extended = extended;
three_extended_expiration = extended.expiration;
});
}
)
.then(function(){
assert.equal(three_original.expiration, 0);
assert.equal(three_extended.expiration, 0);
}).done(done, done);
});

after(function(done){
var err;
var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
for (var i = clients.length - 1; i >= 0; i--) {
clients[i].del(resource, cb);
}
});
});

});
}

0 comments on commit 46116a9

Please sign in to comment.