Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
TheDeveloper committed Sep 1, 2021
2 parents cc85e2a + 4e852db commit 3430a1e
Show file tree
Hide file tree
Showing 16 changed files with 2,609 additions and 124 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
env:
commonjs: true
es2021: true
node: true
extends:
- airbnb-base
parserOptions:
ecmaVersion: 12
rules: {}
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ node_js:
- '0.12'
- '0.10'
- iojs
- '4.2'
- '5.3'
services:
- redis
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
Changelog
---
# [1.0.0](https://github.com/TheDeveloper/warlock/compare/v0.2.0...v1.0.0) (2021-04-02)

**This is a major release and contain breaking changes. Please read this changelog before upgrading.**

### BREAKING CHANGES

* Drop support for node 6.

### Bug Fixes
* Switch deprecated [node-redis-scripty](https://github.com/TheDeveloper/scripty) to [node-redis-script](https://github.com/TheDeveloper/node-redis-script).
* Updated deps.

# v0.2.0

* Merge [#18](https://github.com/TheDeveloper/warlock/pull/18): Pass lock id to `.lock` callback.

# v0.1.3

Expand Down
12 changes: 0 additions & 12 deletions Makefile

This file was deleted.

49 changes: 35 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
warlock
=======

[![Travis](https://travis-ci.org/TheDeveloper/warlock.svg?branch=master)](https://travis-ci.org/TheDeveloper/warlock)
[![Dependency Status](https://david-dm.org/thedeveloper/warlock.svg)](https://david-dm.org/thedeveloper/warlock)
[![Join the chat at https://gitter.im/TheDeveloper/warlock](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/TheDeveloper/warlock?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Battle-hardened distributed locking using redis.
Expand All @@ -19,19 +17,18 @@ Battle-hardened distributed locking using redis.
## Usage

```javascript

var Warlock = require('node-redis-warlock');
var redis = require('redis');
const Warlock = require('node-redis-warlock');
const Redis = require('redis');

// Establish a redis client and pass it to warlock
var redis = redis.createClient();
var warlock = Warlock(redis);
const redis = Redis.createClient();
const warlock = Warlock(redis);

// Set a lock
var key = 'test-lock';
var ttl = 10000; // Lifetime of the lock
const key = 'test-lock';
const ttl = 10000; // Lifetime of the lock

warlock.lock(key, ttl, function(err, unlock){
warlock.lock(key, ttl, (err, unlock) => {
if (err) {
// Something went wrong and we weren't able to set a lock
return;
Expand All @@ -51,12 +48,36 @@ warlock.lock(key, ttl, function(err, unlock){
});

// set a lock optimistically
var key = 'opt-lock';
const key = 'opt-lock';
const ttl = 10000;
const maxAttempts = 4; // Max number of times to try setting the lock before erroring
const wait = 1000; // Time to wait before another attempt if lock already in place
warlock.optimistic(key, ttl, maxAttempts, wait, (err, unlock) => {});

// unlock using the lock id
var key = 'test-lock-2';
var ttl = 10000;
var maxAttempts = 4; // Max number of times to try setting the lock before erroring
var wait = 1000; // Time to wait before another attempt if lock already in place
warlock.optimistic(key, ttl, maxAttempts, wait, function(err, unlock) {});
let lockId;

warlock.lock(key, ttl, (err, _, id) => {
lockId = id;
});

// each client who knows the lockId can release the lock
warlock.unlock(key, lockId, (err, result) => {
if (result == 1) {
// unlocked successfully
}
});

// change a lock's ttl
var key = 'touch-lock';
var ttl = 10000;
var ttl2 = 20000;

warlock.lock(key, ttl, function(err, unlock, id) {
warlock.touch(key, id, ttl2, function(err) {});
});
```

## ProTips
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
redis:
image: redis:3.2-alpine
ports:
- 6386:6379
container_name: warlock-redis

monitor:
image: redis:3.2-alpine
links:
- redis
entrypoint: "/bin/sh"
command: >
"-c" "redis-cli -h warlock-redis -p 6379 monitor"
10 changes: 10 additions & 0 deletions lib/del.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require('path');

const _path = path.resolve(__dirname, './lua/parityDel.lua');
const src = require('fs').readFileSync(_path, { encoding: 'utf-8' });
const { createScript } = require('node-redis-script');

exports.createScript = (redis) => {
const opts = { redis };
return createScript(opts, src);
};
2 changes: 1 addition & 1 deletion lib/lua/parityDel.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
-- Delete a key if content is equal
--
-- KEYS[1] - key
-- KEYS[2] - content
-- ARGV[1] - content
local key = KEYS[1]
local content = ARGV[1]

Expand Down
18 changes: 18 additions & 0 deletions lib/lua/parityRelock.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--
-- Extend the key if content is equal
-- This will extend the lock on the
--
-- KEYS[1] - key
-- KEYS[2] - content
-- KEYS[3] - lock id
local key = KEYS[1]
local ttl = KEYS[2]
local id = KEYS[3]

local value = redis.call('GET', key)

if value == id then
return redis.call('SET', key, id, 'PX', ttl, 'XX');
else
return 0
end
79 changes: 47 additions & 32 deletions lib/warlock.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
var crypto = require('crypto');
var Scripty = require('node-redis-scripty');
var UUID = require('uuid');
const UUID = require('uuid');
const { createScript } = require('./del');

module.exports = function(redis){
var warlock = {};
module.exports = function (redis) {
const warlock = {};

var scripty = new Scripty(redis);
const parityDel = createScript(redis);

warlock.makeKey = function(key) {
return key + ':lock';
warlock.makeKey = function (key) {
return `${key}:lock`;
};

/**
Expand All @@ -17,63 +16,61 @@ module.exports = function(redis){
* @param {integer} ttl Time in milliseconds for the lock to live.
* @param {Function} cb
*/
warlock.lock = function(key, ttl, cb) {
cb = cb || function(){};
warlock.lock = function (key, ttl, cb) {
cb = cb || function () {};

if (typeof key !== 'string') {
return cb(new Error('lock key must be string'));
}

var id;
let id;
UUID.v1(null, (id = new Buffer(16)));
id = id.toString('base64');
redis.set(
warlock.makeKey(key), id,
'PX', ttl, 'NX',
function(err, lockSet) {
(err, lockSet) => {
if (err) return cb(err);

var unlock = lockSet ? warlock.unlock.bind(warlock, key, id) : false;

return cb(err, unlock);
}
const unlock = lockSet ? warlock.unlock.bind(warlock, key, id) : false;
return cb(err, unlock, id);
},
);

return key;
};

warlock.unlock = function(key, id, cb) {
cb = cb || function(){};
warlock.unlock = async (key, id, cb) => {
cb = cb || function () {};

if (typeof key !== 'string') {
return cb(new Error('lock key must be string'));
}

scripty.loadScriptFile(
'parityDel',
__dirname + '/lua/parityDel.lua',
function(err, parityDel){
if (err) return cb(err);

return parityDel.run(1, warlock.makeKey(key), id, cb);
}
);
const numKeys = 1;
const _key = warlock.makeKey(key);
try {
const result = await parityDel(numKeys, _key, id);
cb(null, result);
} catch (e) {
cb(e);
}
};

/**
* Set a lock optimistically (retries until reaching maxAttempts).
*/
warlock.optimistic = function(key, ttl, maxAttempts, wait, cb) {
var attempts = 0;
warlock.optimistic = function (key, ttl, maxAttempts, wait, cb) {
let attempts = 0;

var tryLock = function() {
var tryLock = function () {
attempts += 1;
warlock.lock(key, ttl, function(err, unlock) {
warlock.lock(key, ttl, (err, unlock) => {
if (err) return cb(err);

if (typeof unlock !== 'function') {
if (attempts >= maxAttempts) {
var e = new Error('unable to obtain lock');
const e = new Error('unable to obtain lock');
e.maxAttempts = maxAttempts;
e.key = key;
e.ttl = ttl;
Expand All @@ -90,5 +87,23 @@ module.exports = function(redis){
tryLock();
};

warlock.touch = function(key, id, ttl, cb) {
cb = cb || function (){};

if (typeof key !== 'string') {
return cb(new Error('lock key must be string'));
}

scripty.loadScriptFile(
'parityRelock',
__dirname + '/lua/parityRelock.lua',
function (err, parityRelock) {
if (err) { return cb(err); }
return parityRelock.run(3, warlock.makeKey(key), ttl, id, cb);
}
);
};


return warlock;
};
Loading

0 comments on commit 3430a1e

Please sign in to comment.