Skip to content

Commit 172f822

Browse files
committed
working with 2.2.x need to check version and send null for value on ttl reset for 2.3+
1 parent 86790d1 commit 172f822

File tree

10 files changed

+321
-20
lines changed

10 files changed

+321
-20
lines changed

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
src
22
test
33
coverage
4+
npm-debug.log
5+
.DS_Store
6+
.gitignore
7+
circle.yml

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ SHELL := /bin/bash
66
all: .FORCE
77
babel src -d lib
88

9-
test: .FORCE
9+
unit: .FORCE
1010
mocha test/unit
1111

1212
integration: .FORCE
13-
docker-compose up -d
13+
PREFIX=$(etcd_command_prefix) VERSION=$(etcd_image_version) docker-compose up -d
1414
mocha test/integration
15-
docker-compose down
15+
PREFIX=$(etcd_command_prefix) VERSION=$(etcd_image_version) docker-compose stop || echo 'failed to bring down containers'
16+
PREFIX=$(etcd_command_prefix) VERSION=$(etcd_image_version) docker-compose rm -f || echo 'failed to remove containers'
1617

1718
lint: .FORCE
1819
eslint src

README.md

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,167 @@
1-
# microlock
2-
Simple distributed locking with Etcd for Node.js
1+
# Microlock
2+
A dead simple distributed locking library for Node.js and [etcd](http://github.com/coreos/etcd) (via [node-etcd](https://github.com/stianeikeland/node-etcd))
3+
4+
[![NPM](https://nodei.co/npm/microlock.png)](https://nodei.co/npm/microlock/)
5+
6+
[![CircleCI](https://circleci.com/gh/thebigredgeek/microlock/tree/master.svg?style=shield)](https://circleci.com/gh/thebigredgeek/microlock/tree/master)
7+
8+
9+
## What is a distrbuted lock?
10+
11+
A distributed lock is a mechanism that provides serialized flow control on a context that is acted on by more than one process. These processes typically operate on different machines via Service Oriented Architecture. Each process uses an object called a distributed lock to "lock" access to the shared context so that only one process can act on it at a time, thereby ensuring consistency and preventing race conditions.
12+
13+
## Notes
14+
15+
Microlock is currently compatible with Etd 2.2.x. Within the next two weeks, there will be full support and test coverage for Etcd 2.2.x - 3.x.
16+
17+
## Install
18+
**Requires NodeJS >= 4.0**
19+
20+
```bash
21+
$ npm install microlock
22+
```
23+
24+
## Basic usage
25+
26+
### ES5
27+
```javascript
28+
var os = require('os');
29+
var Etcd = require('node-etcd');
30+
var Microlock = require('microlock');
31+
32+
var key = 'foo'; //name of the lock
33+
var id = os.hostname(); //id of *this* node
34+
var ttl = 5; //5 second lease on lock
35+
36+
var etcd = new Etcd();
37+
var foo = new Microlock.default(etcd, key, id, ttl);
38+
39+
foo.lock().then(function () {
40+
// foo is locked by this node
41+
42+
// do some stuff...
43+
44+
// release the lock
45+
return foo.unlock();
46+
}, function (e) {
47+
if (e instanceof Microlock.AlreadyLockedError) {
48+
// foo is already locked by a different node
49+
}
50+
});
51+
```
52+
53+
### ES2015 (with babel)
54+
```javascript
55+
import { hostname } from 'os';
56+
import Etcd from 'node-etcd';
57+
import Microlock, { AlreadyLockedError } from 'microlock';
58+
59+
const key = 'foo'; //name of the lock
60+
const id = hostname(); //id of *this* node
61+
const ttl = 5; //5 second lease on lock
62+
63+
const etcd = new Etcd();
64+
const foo = new Microlock(etcd, key, id, ttl);
65+
66+
foo.lock().then(() => {
67+
// foo is locked by this node
68+
69+
// do some stuff...
70+
71+
// release the lock
72+
return foo.unlock();
73+
}, (e) => {
74+
if (e instanceof AlreadyLockedError) {
75+
// foo is already locked by a different node
76+
}
77+
});
78+
```
79+
80+
### ES2016/2017 (with babel)
81+
```javascript
82+
83+
import { hostname } from 'os';
84+
import Etcd from 'node-etcd';
85+
import Microlock, { AlreadyLockedError } from 'microlock';
86+
87+
async function main () {
88+
89+
const key = 'foo'; //name of the lock
90+
const id = hostname(); //id of *this* node
91+
const ttl = 5; //5 second lease on lock
92+
93+
const etcd = new Etcd();
94+
const foo = new Microlock(etcd, key, id, ttl);
95+
96+
try {
97+
await foo.lock();
98+
// foo is locked by this node
99+
100+
// do some stuff...
101+
102+
// release the lock
103+
await foo.unlock();
104+
} catch (e) {
105+
if (e instanceof AlreadyLockedError) {
106+
// foo is already locked by a different node
107+
}
108+
}
109+
}
110+
111+
main();
112+
```
113+
114+
## Methods
115+
116+
### Microlock(etcd, key, node_id, [ttl = 1])
117+
Creates a microlock client for a lock key.
118+
119+
```javascript
120+
var Etcd = require('node-etcd');
121+
var Microlock = require('microlock');
122+
123+
var etcd = new Etcd();
124+
var foo = new Microlock.default(microlock, 'foo', 'bar');
125+
```
126+
127+
### .lock()
128+
Attempts to lock the `key` for the `node_id`.
129+
130+
```javascript
131+
foo.lock().then(function () {
132+
// foo is locked by this node
133+
}, function (e) {
134+
if (e instanceof Microlock.AlreadyLockedError) {
135+
// foo is already locked by a different node
136+
}
137+
});
138+
```
139+
140+
### .unlock()
141+
Attempts to release the `key` for the `node_id`.
142+
143+
```javascript
144+
foo.unlock().then(function () {
145+
// foo is unlocked
146+
}, function (e) {
147+
if (e instanceof Microlock.LockNotOwnedError) {
148+
// foo is not locked by `node_id`
149+
}
150+
})
151+
```
152+
153+
### .renew()
154+
Attempts to renew the lock on `key` for the `node_id`.
155+
156+
```javascript
157+
foo.renew().then(function () {
158+
// foo lease is renewed... ttl is refreshed
159+
}, function (e) {
160+
if (e instanceof Microlock.LockNotOwnedError) {
161+
// foo is not locked by `node_id`
162+
}
163+
})
164+
```
165+
166+
### .destroy()
167+
Unbinds listeners/watchers from this client

circle.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ machine:
66
test:
77
override:
88
- make lint
9-
- make test
10-
- make build
11-
- make integration
12-
deployment:
13-
npm:
14-
branch: master
15-
commands:
16-
- make publish
9+
- make unit
10+
- make
11+
- make integration etcd_image_version=v2.2.0
12+
- make integration etcd_image_version=v2.2.1
13+
- make integration etcd_image_version=v2.2.2
14+
- make integration etcd_image_version=v2.2.3
15+
- make integration etcd_image_version=v2.2.4
16+
- make integration etcd_image_version=v2.2.5

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
etcd:
2-
image: quay.io/coreos/etcd
2+
image: quay.io/coreos/etcd:$VERSION
33
ports:
44
- "2379:2379"
5-
command: -advertise-client-urls=http://0.0.0.0:2379 -listen-client-urls=http://0.0.0.0:2379
5+
command: $PREFIX-advertise-client-urls=http://0.0.0.0:2379 -listen-client-urls=http://0.0.0.0:2379

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"eslint": "^2.8.0",
3535
"eslint-plugin-babel": "^3.2.0",
3636
"mocha": "^2.4.5",
37+
"node-etcd": "^5.0.3",
3738
"sinon": "^1.17.3",
3839
"source-map-support": "^0.4.2"
3940
}

src/microlock.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ export default class Microlock extends EventEmitter {
149149
renew () {
150150
return new Promise((resolve, reject) => {
151151
return this.__etcd.set(this.__key, this.__node_id, {
152-
prevExist: true,
153152
prevValue: this.__node_id,
154153
refresh: true,
155154
ttl: this.__ttl

test/integration/microlock_spec.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import Promise from 'bluebird';
4+
import Etcd from 'node-etcd';
5+
6+
import Microlock, {
7+
events,
8+
9+
EtcdClientRequiredError,
10+
KeyRequiredError,
11+
NodeIdRequiredError,
12+
InvalidTtlError,
13+
AlreadyLockedError,
14+
LockNotOwnedError
15+
} from '../../lib/microlock';
16+
17+
describe('microlock', () => {
18+
let etcd = null
19+
, key = null
20+
, node1 = null
21+
, node2 = null
22+
, lock1 = null
23+
, lock2 = null
24+
, ttl = null;
25+
26+
beforeEach(() => {
27+
etcd = new Etcd();
28+
29+
key = 'key';
30+
31+
node1 = 'node_1';
32+
node2 = 'node_2';
33+
34+
ttl = 5;
35+
36+
lock1 = new Microlock(etcd, key, node1, ttl);
37+
lock2 = new Microlock(etcd, key, node2, ttl);
38+
});
39+
40+
afterEach(() => Promise.all([
41+
lock1.unlock(),
42+
lock2.unlock()
43+
]).catch((e) => e));
44+
45+
describe('lock', () => {
46+
context('is locked', () => {
47+
beforeEach(() => lock2.lock());
48+
it('rejects with AlreadyLockedError', (done) => {
49+
lock1
50+
.lock()
51+
.then(() => done(new Error('should have rejected')), (e) => {
52+
expect(e instanceof AlreadyLockedError).to.be.true;
53+
done();
54+
})
55+
.catch(done);
56+
});
57+
});
58+
context('is not locked', () => {
59+
it('resolves', (done) => {
60+
lock1
61+
.lock()
62+
.then(() => done(), () => done(new Error('should have resolved')));
63+
});
64+
});
65+
});
66+
describe('unlock', () => {
67+
context('is locked by current node', () => {
68+
beforeEach(() => lock1.lock());
69+
it('resolves', (done) => {
70+
lock1
71+
.unlock()
72+
.then(() => done(), () => done(new Error('should have resolved')));
73+
});
74+
});
75+
context('is not locked by current node', () => {
76+
beforeEach(() => lock2.lock());
77+
it('rejects with LockNotOwnedError', (done) => {
78+
lock1
79+
.unlock()
80+
.then(() => done(new Error('should have rejected')), (e) => {
81+
expect(e instanceof LockNotOwnedError).to.be.true;
82+
done();
83+
})
84+
.catch(done);
85+
});
86+
});
87+
context('is not locked', () => {
88+
it('rejects with LockNotOwnedError', (done) => {
89+
lock1
90+
.unlock()
91+
.then(() => done(new Error('should have rejected')), (e) => {
92+
expect(e instanceof LockNotOwnedError).to.be.true;
93+
done();
94+
})
95+
.catch(done);
96+
});
97+
});
98+
});
99+
describe('renew', () => {
100+
context('is locked by current node', () => {
101+
beforeEach(() => lock1.lock());
102+
it('resolves', (done) => {
103+
lock1
104+
.renew()
105+
.then(() => done(), () => done(new Error('should have resolved')));
106+
});
107+
});
108+
context('is not locked by current node', () => {
109+
beforeEach(() => lock2.lock());
110+
it('rejects with LockNotOwnedError', (done) => {
111+
lock1
112+
.renew()
113+
.then(() => done(new Error('should have rejected')), (e) => {
114+
expect(e instanceof LockNotOwnedError).to.be.true;
115+
done();
116+
})
117+
.catch(done);
118+
});
119+
});
120+
context('is not locked', () => {
121+
it('rejects with LockNotOwnedError', (done) => {
122+
lock1
123+
.renew()
124+
.then(() => done(new Error('should have rejected')), (e) => {
125+
expect(e instanceof LockNotOwnedError).to.be.true;
126+
done();
127+
})
128+
.catch(done);
129+
});
130+
});
131+
});
132+
});

test/setup.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require('source-map-support').install();
1+
//require('source-map-support').install();
22
require('babel-register');
33
require('babel-polyfill');
44

@@ -7,5 +7,5 @@ function kill (e) {
77
process.exit(1);
88
}
99

10-
process.on('uncaughtException', kill);
11-
process.on('unhandledRejection', kill);
10+
//process.on('uncaughtException', kill);
11+
//process.on('unhandledRejection', kill);

test/unit/microlock_spec.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ describe('microlock', () => {
206206
microlock.renew();
207207
expect(etcd.set.calledWith(key, node_id, {
208208
ttl,
209-
prevExist: true,
210209
prevValue: node_id,
211210
refresh: true
212211
})).to.be.true;

0 commit comments

Comments
 (0)