effect-distributed-lock 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/push.ts +109 -0
- package/package.json +1 -1
- package/src/DistributedSemaphore.ts +7 -8
- package/src/RedisBacking.ts +4 -4
- package/redis-semaphore/.codeclimate.yml +0 -5
- package/redis-semaphore/.fossa.yml +0 -14
- package/redis-semaphore/.github/dependabot.yml +0 -6
- package/redis-semaphore/.github/workflows/branches.yml +0 -39
- package/redis-semaphore/.github/workflows/pull-requests.yml +0 -35
- package/redis-semaphore/.mocharc.yaml +0 -6
- package/redis-semaphore/.prettierrc +0 -6
- package/redis-semaphore/.snyk +0 -4
- package/redis-semaphore/.yarnrc.yml +0 -2
- package/redis-semaphore/CHANGELOG.md +0 -70
- package/redis-semaphore/Dockerfile +0 -5
- package/redis-semaphore/LICENSE +0 -21
- package/redis-semaphore/README.md +0 -445
- package/redis-semaphore/docker-compose.yml +0 -31
- package/redis-semaphore/eslint.config.mjs +0 -73
- package/redis-semaphore/package.json +0 -79
- package/redis-semaphore/setup-redis-servers.sh +0 -2
- package/redis-semaphore/src/Lock.ts +0 -172
- package/redis-semaphore/src/RedisMultiSemaphore.ts +0 -56
- package/redis-semaphore/src/RedisMutex.ts +0 -45
- package/redis-semaphore/src/RedisSemaphore.ts +0 -49
- package/redis-semaphore/src/RedlockMultiSemaphore.ts +0 -56
- package/redis-semaphore/src/RedlockMutex.ts +0 -52
- package/redis-semaphore/src/RedlockSemaphore.ts +0 -49
- package/redis-semaphore/src/errors/LostLockError.ts +0 -1
- package/redis-semaphore/src/errors/TimeoutError.ts +0 -1
- package/redis-semaphore/src/index.ts +0 -23
- package/redis-semaphore/src/misc.ts +0 -12
- package/redis-semaphore/src/multiSemaphore/acquire/index.ts +0 -53
- package/redis-semaphore/src/multiSemaphore/acquire/lua.ts +0 -31
- package/redis-semaphore/src/multiSemaphore/refresh/index.ts +0 -32
- package/redis-semaphore/src/multiSemaphore/refresh/lua.ts +0 -31
- package/redis-semaphore/src/multiSemaphore/release/index.ts +0 -22
- package/redis-semaphore/src/multiSemaphore/release/lua.ts +0 -17
- package/redis-semaphore/src/mutex/acquire.ts +0 -42
- package/redis-semaphore/src/mutex/refresh.ts +0 -37
- package/redis-semaphore/src/mutex/release.ts +0 -30
- package/redis-semaphore/src/redlockMultiSemaphore/acquire.ts +0 -56
- package/redis-semaphore/src/redlockMultiSemaphore/refresh.ts +0 -68
- package/redis-semaphore/src/redlockMultiSemaphore/release.ts +0 -19
- package/redis-semaphore/src/redlockMutex/acquire.ts +0 -54
- package/redis-semaphore/src/redlockMutex/refresh.ts +0 -53
- package/redis-semaphore/src/redlockMutex/release.ts +0 -19
- package/redis-semaphore/src/redlockSemaphore/acquire.ts +0 -55
- package/redis-semaphore/src/redlockSemaphore/refresh.ts +0 -60
- package/redis-semaphore/src/redlockSemaphore/release.ts +0 -18
- package/redis-semaphore/src/semaphore/acquire/index.ts +0 -52
- package/redis-semaphore/src/semaphore/acquire/lua.ts +0 -25
- package/redis-semaphore/src/semaphore/refresh/index.ts +0 -31
- package/redis-semaphore/src/semaphore/refresh/lua.ts +0 -25
- package/redis-semaphore/src/semaphore/release.ts +0 -14
- package/redis-semaphore/src/types.ts +0 -63
- package/redis-semaphore/src/utils/createEval.ts +0 -45
- package/redis-semaphore/src/utils/index.ts +0 -13
- package/redis-semaphore/src/utils/redlock.ts +0 -7
- package/redis-semaphore/test/init.test.ts +0 -9
- package/redis-semaphore/test/redisClient.ts +0 -82
- package/redis-semaphore/test/setup.ts +0 -6
- package/redis-semaphore/test/shell.test.ts +0 -15
- package/redis-semaphore/test/shell.ts +0 -48
- package/redis-semaphore/test/src/Lock.test.ts +0 -37
- package/redis-semaphore/test/src/RedisMultiSemaphore.test.ts +0 -425
- package/redis-semaphore/test/src/RedisMutex.test.ts +0 -334
- package/redis-semaphore/test/src/RedisSemaphore.test.ts +0 -367
- package/redis-semaphore/test/src/RedlockMultiSemaphore.test.ts +0 -671
- package/redis-semaphore/test/src/RedlockMutex.test.ts +0 -328
- package/redis-semaphore/test/src/RedlockSemaphore.test.ts +0 -579
- package/redis-semaphore/test/src/index.test.ts +0 -22
- package/redis-semaphore/test/src/multiSemaphore/acquire/index.test.ts +0 -51
- package/redis-semaphore/test/src/multiSemaphore/acquire/internal.test.ts +0 -67
- package/redis-semaphore/test/src/multiSemaphore/refresh/index.test.ts +0 -52
- package/redis-semaphore/test/src/multiSemaphore/release/index.test.ts +0 -18
- package/redis-semaphore/test/src/mutex/acquire.test.ts +0 -78
- package/redis-semaphore/test/src/mutex/refresh.test.ts +0 -22
- package/redis-semaphore/test/src/mutex/release.test.ts +0 -17
- package/redis-semaphore/test/src/redlockMutex/acquire.test.ts +0 -90
- package/redis-semaphore/test/src/redlockMutex/refresh.test.ts +0 -27
- package/redis-semaphore/test/src/redlockMutex/release.test.ts +0 -17
- package/redis-semaphore/test/src/semaphore/acquire/index.test.ts +0 -49
- package/redis-semaphore/test/src/semaphore/acquire/internal.test.ts +0 -65
- package/redis-semaphore/test/src/semaphore/refresh/index.test.ts +0 -44
- package/redis-semaphore/test/src/semaphore/release.test.ts +0 -18
- package/redis-semaphore/test/src/utils/eval.test.ts +0 -22
- package/redis-semaphore/test/src/utils/index.test.ts +0 -19
- package/redis-semaphore/test/src/utils/redlock.test.ts +0 -31
- package/redis-semaphore/test/unhandledRejection.ts +0 -28
- package/redis-semaphore/tsconfig.build-commonjs.json +0 -9
- package/redis-semaphore/tsconfig.build-es.json +0 -9
- package/redis-semaphore/tsconfig.json +0 -11
- package/redis-semaphore/yarn.lock +0 -5338
- /package/examples/{index.ts → kitchen-sink.ts} +0 -0
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { acquireLua } from '../../../../src/multiSemaphore/acquire/lua'
|
|
4
|
-
import { client1 as client } from '../../../redisClient'
|
|
5
|
-
|
|
6
|
-
interface Options {
|
|
7
|
-
identifier: string
|
|
8
|
-
lockTimeout: number
|
|
9
|
-
now: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const opts = (id: string, nowOffset = 0): Options => ({
|
|
13
|
-
identifier: id,
|
|
14
|
-
lockTimeout: 500,
|
|
15
|
-
now: new Date().getTime() + nowOffset
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
async function acquire(options: Options) {
|
|
19
|
-
const { identifier, lockTimeout, now } = options
|
|
20
|
-
return await acquireLua(client, ['key', 1, 1, identifier, lockTimeout, now])
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('multiSemaphore acquire internal', () => {
|
|
24
|
-
it('should return 1 for success acquire', async () => {
|
|
25
|
-
const result = await acquire(opts('111'))
|
|
26
|
-
expect(result).to.be.eql(1)
|
|
27
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['111_0'])
|
|
28
|
-
})
|
|
29
|
-
it('should return 0 for failure acquire', async () => {
|
|
30
|
-
const result1 = await acquire(opts('111'))
|
|
31
|
-
const result2 = await acquire(opts('112'))
|
|
32
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['111_0'])
|
|
33
|
-
expect(result1).to.be.eql(1)
|
|
34
|
-
expect(result2).to.be.eql(0)
|
|
35
|
-
})
|
|
36
|
-
describe('TIME SHIFT case', () => {
|
|
37
|
-
it('should handle time difference less than lockTimeout (nodeA has faster clocks)', async () => {
|
|
38
|
-
// lockTimeout = 500ms
|
|
39
|
-
// nodeA is for 450ms faster than nodeB
|
|
40
|
-
const resultA = await acquire(opts('111', 450))
|
|
41
|
-
const resultB = await acquire(opts('112', 0))
|
|
42
|
-
expect(resultA).to.be.eql(1)
|
|
43
|
-
expect(resultB).to.be.eql(0)
|
|
44
|
-
})
|
|
45
|
-
it('should handle time difference less than lockTimeout (nodeA has slower clocks)', async () => {
|
|
46
|
-
// lockTimeout = 500ms
|
|
47
|
-
// nodeB is for 450ms faster than nodeA
|
|
48
|
-
const resultA = await acquire(opts('111', 0))
|
|
49
|
-
const resultB = await acquire(opts('112', 450))
|
|
50
|
-
expect(resultA).to.be.eql(1)
|
|
51
|
-
expect(resultB).to.be.eql(0)
|
|
52
|
-
})
|
|
53
|
-
it('cant handle time difference greater than lockTimeout (nodeA has slower clocks)', async () => {
|
|
54
|
-
// lockTimeout = 500ms
|
|
55
|
-
// nodeB is for 550ms faster than nodeA
|
|
56
|
-
const resultA = await acquire(opts('111', 0))
|
|
57
|
-
const resultB = await acquire(opts('112', 550))
|
|
58
|
-
expect(resultA).to.be.eql(1)
|
|
59
|
-
expect(resultB).to.be.eql(1) // Semaphore stealed...
|
|
60
|
-
|
|
61
|
-
// This happens due removing "expired" nodeA lock (at nodeB "now" nodeA lock has been expired 50ms ago)
|
|
62
|
-
// Unfortunatelly "fair" semaphore described here
|
|
63
|
-
// https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/6-3-1-building-a-basic-counting-semaphore/
|
|
64
|
-
// also has the same problem
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
})
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Options,
|
|
5
|
-
refreshSemaphore as refresh
|
|
6
|
-
} from '../../../../src/multiSemaphore/refresh/index'
|
|
7
|
-
import { client1 as client } from '../../../redisClient'
|
|
8
|
-
|
|
9
|
-
const opts = (id: string): Options => ({
|
|
10
|
-
identifier: id,
|
|
11
|
-
lockTimeout: 100
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('multiSemaphore refresh', () => {
|
|
15
|
-
it('should return false if resource is already acquired', async () => {
|
|
16
|
-
const now = '' + (Date.now() - 10)
|
|
17
|
-
await client.zadd('key', now, '222', now, '333', now, '444')
|
|
18
|
-
const result = await refresh(client, 'key', 3, 2, opts('111'))
|
|
19
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['222', '333', '444'])
|
|
20
|
-
expect(result).to.be.false
|
|
21
|
-
})
|
|
22
|
-
it('should return false if resource is already acquired, but some expired', async () => {
|
|
23
|
-
const now = '' + (Date.now() - 10)
|
|
24
|
-
const oldNow = '' + (Date.now() - 10000)
|
|
25
|
-
await client.zadd('key', oldNow, '222', oldNow, '333', now, '444')
|
|
26
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['222', '333', '444'])
|
|
27
|
-
const result = await refresh(client, 'key', 3, 2, opts('111'))
|
|
28
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['444'])
|
|
29
|
-
expect(result).to.be.false
|
|
30
|
-
})
|
|
31
|
-
it('should return false if resource is not acquired', async () => {
|
|
32
|
-
const result = await refresh(client, 'key', 3, 2, opts('111'))
|
|
33
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql([])
|
|
34
|
-
expect(result).to.be.false
|
|
35
|
-
})
|
|
36
|
-
it('should return true for success refresh', async () => {
|
|
37
|
-
const now = '' + (Date.now() - 10)
|
|
38
|
-
await client.zadd('key', now, '111_0', now, '111_1', now, '333')
|
|
39
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql([
|
|
40
|
-
'111_0',
|
|
41
|
-
'111_1',
|
|
42
|
-
'333'
|
|
43
|
-
])
|
|
44
|
-
const result = await refresh(client, 'key', 3, 2, opts('111'))
|
|
45
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql([
|
|
46
|
-
'333',
|
|
47
|
-
'111_0',
|
|
48
|
-
'111_1'
|
|
49
|
-
])
|
|
50
|
-
expect(result).to.be.true
|
|
51
|
-
})
|
|
52
|
-
})
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { releaseSemaphore as release } from '../../../../src/multiSemaphore/release/index'
|
|
4
|
-
import { client1 as client } from '../../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('multiSemaphore release', () => {
|
|
7
|
-
it('should remove key after success release', async () => {
|
|
8
|
-
await client.zadd('key', '' + Date.now(), '111_0')
|
|
9
|
-
expect(await client.zcard('key')).to.be.eql(1)
|
|
10
|
-
await release(client, 'key', 1, '111')
|
|
11
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
12
|
-
})
|
|
13
|
-
it('should do nothing if resource is not locked', async () => {
|
|
14
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
15
|
-
await release(client, 'key', 1, '111')
|
|
16
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
17
|
-
})
|
|
18
|
-
})
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { acquireMutex as acquire, Options } from '../../../src/mutex/acquire'
|
|
4
|
-
import { client1 as client } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
const opts = (id: string, overrides?: Partial<Options>): Options => ({
|
|
7
|
-
identifier: id,
|
|
8
|
-
acquireTimeout: 50,
|
|
9
|
-
acquireAttemptsLimit: Number.POSITIVE_INFINITY,
|
|
10
|
-
lockTimeout: 100,
|
|
11
|
-
retryInterval: 10,
|
|
12
|
-
...overrides
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
describe('mutex acquire', () => {
|
|
16
|
-
it('should return true for success lock', async () => {
|
|
17
|
-
const result = await acquire(client, 'key', opts('111'))
|
|
18
|
-
expect(result).to.be.true
|
|
19
|
-
})
|
|
20
|
-
it('should return false when timeout', async () => {
|
|
21
|
-
const result1 = await acquire(client, 'key', opts('111'))
|
|
22
|
-
const result2 = await acquire(client, 'key', opts('222'))
|
|
23
|
-
expect(result1).to.be.true
|
|
24
|
-
expect(result2).to.be.false
|
|
25
|
-
})
|
|
26
|
-
it('should return false after acquireAttemptsLimit', async () => {
|
|
27
|
-
const result1 = await acquire(client, 'key', opts('111'))
|
|
28
|
-
const result2 = await acquire(
|
|
29
|
-
client,
|
|
30
|
-
'key',
|
|
31
|
-
opts('222', {
|
|
32
|
-
acquireAttemptsLimit: 1,
|
|
33
|
-
acquireTimeout: Number.POSITIVE_INFINITY
|
|
34
|
-
})
|
|
35
|
-
)
|
|
36
|
-
expect(result1).to.be.true
|
|
37
|
-
expect(result2).to.be.false
|
|
38
|
-
})
|
|
39
|
-
it('should set identifier for key', async () => {
|
|
40
|
-
await acquire(client, 'key1', opts('111'))
|
|
41
|
-
const value = await client.get('key1')
|
|
42
|
-
expect(value).to.be.eql('111')
|
|
43
|
-
})
|
|
44
|
-
it('should set TTL for key', async () => {
|
|
45
|
-
await acquire(client, 'key2', opts('111'))
|
|
46
|
-
const ttl = await client.pttl('key2')
|
|
47
|
-
expect(ttl).to.be.gte(90)
|
|
48
|
-
expect(ttl).to.be.lte(100)
|
|
49
|
-
})
|
|
50
|
-
it('should wait for auto-release', async () => {
|
|
51
|
-
const start1 = Date.now()
|
|
52
|
-
await acquire(client, 'key', opts('111'))
|
|
53
|
-
const start2 = Date.now()
|
|
54
|
-
await acquire(client, 'key', opts('222'))
|
|
55
|
-
const now = Date.now()
|
|
56
|
-
expect(start2 - start1).to.be.gte(0)
|
|
57
|
-
expect(start2 - start1).to.be.lt(10)
|
|
58
|
-
expect(now - start1).to.be.gte(50)
|
|
59
|
-
expect(now - start2).to.be.gte(50)
|
|
60
|
-
})
|
|
61
|
-
it('should wait per key', async () => {
|
|
62
|
-
const start1 = Date.now()
|
|
63
|
-
await Promise.all([
|
|
64
|
-
acquire(client, 'key1', opts('a1')),
|
|
65
|
-
acquire(client, 'key2', opts('a2'))
|
|
66
|
-
])
|
|
67
|
-
const start2 = Date.now()
|
|
68
|
-
await Promise.all([
|
|
69
|
-
acquire(client, 'key1', opts('b1')),
|
|
70
|
-
acquire(client, 'key2', opts('b2'))
|
|
71
|
-
])
|
|
72
|
-
const now = Date.now()
|
|
73
|
-
expect(start2 - start1).to.be.gte(0)
|
|
74
|
-
expect(start2 - start1).to.be.lt(10)
|
|
75
|
-
expect(now - start1).to.be.gte(50)
|
|
76
|
-
expect(now - start2).to.be.gte(50)
|
|
77
|
-
})
|
|
78
|
-
})
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { refreshMutex as refresh } from '../../../src/mutex/refresh'
|
|
4
|
-
import { client1 as client } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('mutex refresh', () => {
|
|
7
|
-
it('should return false if resource is already acquired by different instance', async () => {
|
|
8
|
-
await client.set('key', '222')
|
|
9
|
-
const result = await refresh(client, 'key', '111', 10000)
|
|
10
|
-
expect(result).to.be.false
|
|
11
|
-
})
|
|
12
|
-
it('should return false if resource is not acquired', async () => {
|
|
13
|
-
const result = await refresh(client, 'key', '111', 10000)
|
|
14
|
-
expect(result).to.be.false
|
|
15
|
-
})
|
|
16
|
-
it('should return true for success refresh', async () => {
|
|
17
|
-
await client.set('key', '111')
|
|
18
|
-
const result = await refresh(client, 'key', '111', 20000)
|
|
19
|
-
expect(result).to.be.true
|
|
20
|
-
expect(await client.pttl('key')).to.be.gte(10000)
|
|
21
|
-
})
|
|
22
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { releaseMutex as release } from '../../../src/mutex/release'
|
|
4
|
-
import { client1 as client } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('Mutex release', () => {
|
|
7
|
-
it('should remove key after release', async () => {
|
|
8
|
-
await client.set('key', '111')
|
|
9
|
-
await release(client, 'key', '111')
|
|
10
|
-
expect(await client.get('key')).to.be.eql(null)
|
|
11
|
-
})
|
|
12
|
-
it('should do nothing if resource is not locked', async () => {
|
|
13
|
-
expect(await client.get('key')).to.be.eql(null)
|
|
14
|
-
await release(client, 'key', '111')
|
|
15
|
-
expect(await client.get('key')).to.be.eql(null)
|
|
16
|
-
})
|
|
17
|
-
})
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
acquireRedlockMutex as acquire,
|
|
5
|
-
Options
|
|
6
|
-
} from '../../../src/redlockMutex/acquire'
|
|
7
|
-
import { allClients } from '../../redisClient'
|
|
8
|
-
|
|
9
|
-
const opts = (id: string, overrides?: Partial<Options>): Options => ({
|
|
10
|
-
identifier: id,
|
|
11
|
-
acquireTimeout: 50,
|
|
12
|
-
acquireAttemptsLimit: Number.POSITIVE_INFINITY,
|
|
13
|
-
lockTimeout: 100,
|
|
14
|
-
retryInterval: 10,
|
|
15
|
-
...overrides
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('redlockMutex acquire', () => {
|
|
19
|
-
it('should return true for success lock', async () => {
|
|
20
|
-
const result = await acquire(allClients, 'key', opts('111'))
|
|
21
|
-
expect(result).to.be.true
|
|
22
|
-
})
|
|
23
|
-
it('should return false when timeout', async () => {
|
|
24
|
-
const result1 = await acquire(allClients, 'key', opts('111'))
|
|
25
|
-
const result2 = await acquire(allClients, 'key', opts('222'))
|
|
26
|
-
expect(result1).to.be.true
|
|
27
|
-
expect(result2).to.be.false
|
|
28
|
-
})
|
|
29
|
-
it('should return false after acquireAttemptsLimit', async () => {
|
|
30
|
-
const result1 = await acquire(allClients, 'key', opts('111'))
|
|
31
|
-
const result2 = await acquire(
|
|
32
|
-
allClients,
|
|
33
|
-
'key',
|
|
34
|
-
opts('222', {
|
|
35
|
-
acquireAttemptsLimit: 1,
|
|
36
|
-
acquireTimeout: Number.POSITIVE_INFINITY
|
|
37
|
-
})
|
|
38
|
-
)
|
|
39
|
-
expect(result1).to.be.true
|
|
40
|
-
expect(result2).to.be.false
|
|
41
|
-
})
|
|
42
|
-
it('should set identifier for key', async () => {
|
|
43
|
-
await acquire(allClients, 'key1', opts('111'))
|
|
44
|
-
const values = await Promise.all(
|
|
45
|
-
allClients.map(client => client.get('key1'))
|
|
46
|
-
)
|
|
47
|
-
expect(values).to.be.eql(['111', '111', '111'])
|
|
48
|
-
})
|
|
49
|
-
it('should set TTL for key', async () => {
|
|
50
|
-
await acquire(allClients, 'key2', opts('111'))
|
|
51
|
-
const ttls = await Promise.all(
|
|
52
|
-
allClients.map(client => client.pttl('key2'))
|
|
53
|
-
)
|
|
54
|
-
for (const ttl of ttls) {
|
|
55
|
-
if (ttl === -2) {
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
expect(ttl).to.be.gte(90)
|
|
59
|
-
expect(ttl).to.be.lte(100)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
it('should wait for auto-release', async () => {
|
|
63
|
-
const start1 = Date.now()
|
|
64
|
-
await acquire(allClients, 'key', opts('111'))
|
|
65
|
-
const start2 = Date.now()
|
|
66
|
-
await acquire(allClients, 'key', opts('222'))
|
|
67
|
-
const now = Date.now()
|
|
68
|
-
expect(start2 - start1).to.be.gte(0)
|
|
69
|
-
expect(start2 - start1).to.be.lt(10)
|
|
70
|
-
expect(now - start1).to.be.gte(50)
|
|
71
|
-
expect(now - start2).to.be.gte(50)
|
|
72
|
-
})
|
|
73
|
-
it('should wait per key', async () => {
|
|
74
|
-
const start1 = Date.now()
|
|
75
|
-
await Promise.all([
|
|
76
|
-
acquire(allClients, 'key1', opts('a1')),
|
|
77
|
-
acquire(allClients, 'key2', opts('a2'))
|
|
78
|
-
])
|
|
79
|
-
const start2 = Date.now()
|
|
80
|
-
await Promise.all([
|
|
81
|
-
acquire(allClients, 'key1', opts('b1')),
|
|
82
|
-
acquire(allClients, 'key2', opts('b2'))
|
|
83
|
-
])
|
|
84
|
-
const now = Date.now()
|
|
85
|
-
expect(start2 - start1).to.be.gte(0)
|
|
86
|
-
expect(start2 - start1).to.be.lt(10)
|
|
87
|
-
expect(now - start1).to.be.gte(50)
|
|
88
|
-
expect(now - start2).to.be.gte(50)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { refreshRedlockMutex as refresh } from '../../../src/redlockMutex/refresh'
|
|
4
|
-
import { allClients, client1, client2, client3 } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('redlockMutex refresh', () => {
|
|
7
|
-
it('should return false if resource is acquired by different instance on quorum', async () => {
|
|
8
|
-
await client1.set('key', '111')
|
|
9
|
-
await client2.set('key', '222')
|
|
10
|
-
await client3.set('key', '222')
|
|
11
|
-
const result = await refresh(allClients, 'key', '111', 10000)
|
|
12
|
-
expect(result).to.be.false
|
|
13
|
-
})
|
|
14
|
-
it('should return true if resource is acquired on quorum', async () => {
|
|
15
|
-
await client1.set('key', '111')
|
|
16
|
-
await client2.set('key', '111')
|
|
17
|
-
const result = await refresh(allClients, 'key', '111', 20000)
|
|
18
|
-
expect(result).to.be.true
|
|
19
|
-
expect(await client1.pttl('key')).to.be.gte(10000)
|
|
20
|
-
expect(await client2.pttl('key')).to.be.gte(10000)
|
|
21
|
-
})
|
|
22
|
-
it('should return false if resource is not acquired on quorum', async () => {
|
|
23
|
-
await client1.set('key', '111')
|
|
24
|
-
const result = await refresh(allClients, 'key', '111', 10000)
|
|
25
|
-
expect(result).to.be.false
|
|
26
|
-
})
|
|
27
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { releaseRedlockMutex as release } from '../../../src/redlockMutex/release'
|
|
4
|
-
import { allClients, client1 } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('redlockMutex release', () => {
|
|
7
|
-
it('should remove key after release', async () => {
|
|
8
|
-
await client1.set('key', '111')
|
|
9
|
-
await release(allClients, 'key', '111')
|
|
10
|
-
expect(await client1.get('key')).to.be.eql(null)
|
|
11
|
-
})
|
|
12
|
-
it('should do nothing if resource is not locked', async () => {
|
|
13
|
-
expect(await client1.get('key')).to.be.eql(null)
|
|
14
|
-
await release(allClients, 'key', '111')
|
|
15
|
-
expect(await client1.get('key')).to.be.eql(null)
|
|
16
|
-
})
|
|
17
|
-
})
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
acquireSemaphore as acquire,
|
|
5
|
-
Options
|
|
6
|
-
} from '../../../../src/semaphore/acquire/index'
|
|
7
|
-
import { client1 as client } from '../../../redisClient'
|
|
8
|
-
|
|
9
|
-
const opts = (id: string, overrides?: Partial<Options>): Options => ({
|
|
10
|
-
identifier: id,
|
|
11
|
-
acquireTimeout: 50,
|
|
12
|
-
acquireAttemptsLimit: Number.POSITIVE_INFINITY,
|
|
13
|
-
lockTimeout: 100,
|
|
14
|
-
retryInterval: 10,
|
|
15
|
-
...overrides
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('semaphore acquire', () => {
|
|
19
|
-
it('should return true for success acquire', async () => {
|
|
20
|
-
const result = await acquire(client, 'key', 1, opts('111'))
|
|
21
|
-
expect(result).to.be.true
|
|
22
|
-
})
|
|
23
|
-
it('should return false when timeout', async () => {
|
|
24
|
-
const result1 = await acquire(client, 'key', 2, opts('111')) // expire after 100ms
|
|
25
|
-
const result2 = await acquire(client, 'key', 2, opts('112')) // expire after 100ms
|
|
26
|
-
const result3 = await acquire(client, 'key', 2, opts('113')) // timeout after 50ms
|
|
27
|
-
|
|
28
|
-
expect(result1).to.be.true
|
|
29
|
-
expect(result2).to.be.true
|
|
30
|
-
expect(result3).to.be.false
|
|
31
|
-
})
|
|
32
|
-
it('should return false after acquireAttemptsLimit', async () => {
|
|
33
|
-
const result1 = await acquire(client, 'key', 2, opts('111')) // expire after 100ms
|
|
34
|
-
const result2 = await acquire(client, 'key', 2, opts('112')) // expire after 100ms
|
|
35
|
-
const result3 = await acquire(
|
|
36
|
-
client,
|
|
37
|
-
'key',
|
|
38
|
-
2,
|
|
39
|
-
opts('113', {
|
|
40
|
-
acquireAttemptsLimit: 1,
|
|
41
|
-
acquireTimeout: Number.POSITIVE_INFINITY
|
|
42
|
-
})
|
|
43
|
-
) // no timeout, acquire limit = 1
|
|
44
|
-
|
|
45
|
-
expect(result1).to.be.true
|
|
46
|
-
expect(result2).to.be.true
|
|
47
|
-
expect(result3).to.be.false
|
|
48
|
-
})
|
|
49
|
-
})
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { acquireLua } from '../../../../src/semaphore/acquire/lua'
|
|
4
|
-
import { client1 as client } from '../../../redisClient'
|
|
5
|
-
|
|
6
|
-
interface Options {
|
|
7
|
-
identifier: string
|
|
8
|
-
lockTimeout: number
|
|
9
|
-
now: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const opts = (id: string, nowOffset = 0): Options => ({
|
|
13
|
-
identifier: id,
|
|
14
|
-
lockTimeout: 500,
|
|
15
|
-
now: new Date().getTime() + nowOffset
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
async function acquire(options: Options) {
|
|
19
|
-
const { identifier, lockTimeout, now } = options
|
|
20
|
-
return await acquireLua(client, ['key', 1, identifier, lockTimeout, now])
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('semaphore acquire internal', () => {
|
|
24
|
-
it('should return 1 for success acquire', async () => {
|
|
25
|
-
const result = await acquire(opts('111'))
|
|
26
|
-
expect(result).to.be.eql(1)
|
|
27
|
-
})
|
|
28
|
-
it('should return 0 for failure acquire', async () => {
|
|
29
|
-
const result1 = await acquire(opts('111'))
|
|
30
|
-
const result2 = await acquire(opts('112'))
|
|
31
|
-
expect(result1).to.be.eql(1)
|
|
32
|
-
expect(result2).to.be.eql(0)
|
|
33
|
-
})
|
|
34
|
-
describe('TIME SHIFT case', () => {
|
|
35
|
-
it('should handle time difference less than lockTimeout (nodeA has faster clocks)', async () => {
|
|
36
|
-
// lockTimeout = 500ms
|
|
37
|
-
// nodeA is for 450ms faster than nodeB
|
|
38
|
-
const resultA = await acquire(opts('111', 450))
|
|
39
|
-
const resultB = await acquire(opts('112', 0))
|
|
40
|
-
expect(resultA).to.be.eql(1)
|
|
41
|
-
expect(resultB).to.be.eql(0)
|
|
42
|
-
})
|
|
43
|
-
it('should handle time difference less than lockTimeout (nodeA has slower clocks)', async () => {
|
|
44
|
-
// lockTimeout = 500ms
|
|
45
|
-
// nodeB is for 450ms faster than nodeA
|
|
46
|
-
const resultA = await acquire(opts('111', 0))
|
|
47
|
-
const resultB = await acquire(opts('112', 450))
|
|
48
|
-
expect(resultA).to.be.eql(1)
|
|
49
|
-
expect(resultB).to.be.eql(0)
|
|
50
|
-
})
|
|
51
|
-
it('cant handle time difference greater than lockTimeout (nodeA has slower clocks)', async () => {
|
|
52
|
-
// lockTimeout = 500ms
|
|
53
|
-
// nodeB is for 550ms faster than nodeA
|
|
54
|
-
const resultA = await acquire(opts('111', 0))
|
|
55
|
-
const resultB = await acquire(opts('112', 550))
|
|
56
|
-
expect(resultA).to.be.eql(1)
|
|
57
|
-
expect(resultB).to.be.eql(1) // Semaphore stealed...
|
|
58
|
-
|
|
59
|
-
// This happens due removing "expired" nodeA lock (at nodeB "now" nodeA lock has been expired 50ms ago)
|
|
60
|
-
// Unfortunatelly "fair" semaphore described here
|
|
61
|
-
// https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/6-3-1-building-a-basic-counting-semaphore/
|
|
62
|
-
// also has the same problem
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
})
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Options,
|
|
5
|
-
refreshSemaphore as refresh
|
|
6
|
-
} from '../../../../src/semaphore/refresh/index'
|
|
7
|
-
import { client1 as client } from '../../../redisClient'
|
|
8
|
-
|
|
9
|
-
const opts = (id: string): Options => ({
|
|
10
|
-
identifier: id,
|
|
11
|
-
lockTimeout: 100
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('semaphore refresh', () => {
|
|
15
|
-
it('should return false if resource is already acquired', async () => {
|
|
16
|
-
const now = '' + (Date.now() - 10)
|
|
17
|
-
await client.zadd('key', now, '222', now, '333', now, '444')
|
|
18
|
-
const result = await refresh(client, 'key', 3, opts('111'))
|
|
19
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['222', '333', '444'])
|
|
20
|
-
expect(result).to.be.false
|
|
21
|
-
})
|
|
22
|
-
it('should return false if resource is already acquired, but some expired', async () => {
|
|
23
|
-
const now = '' + (Date.now() - 10)
|
|
24
|
-
const oldNow = '' + (Date.now() - 10000)
|
|
25
|
-
await client.zadd('key', oldNow, '222', now, '333', now, '444')
|
|
26
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['222', '333', '444'])
|
|
27
|
-
const result = await refresh(client, 'key', 3, opts('111'))
|
|
28
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['333', '444'])
|
|
29
|
-
expect(result).to.be.false
|
|
30
|
-
})
|
|
31
|
-
it('should return false if resource is not acquired', async () => {
|
|
32
|
-
const result = await refresh(client, 'key', 3, opts('111'))
|
|
33
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql([])
|
|
34
|
-
expect(result).to.be.false
|
|
35
|
-
})
|
|
36
|
-
it('should return true for success refresh', async () => {
|
|
37
|
-
const now = '' + (Date.now() - 10)
|
|
38
|
-
await client.zadd('key', now, '111', now, '222', now, '333')
|
|
39
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['111', '222', '333'])
|
|
40
|
-
const result = await refresh(client, 'key', 3, opts('111'))
|
|
41
|
-
expect(await client.zrange('key', 0, -1)).to.be.eql(['222', '333', '111'])
|
|
42
|
-
expect(result).to.be.true
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { releaseSemaphore as release } from '../../../src/semaphore/release'
|
|
4
|
-
import { client1 as client } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('semaphore release', () => {
|
|
7
|
-
it('should remove key after success release', async () => {
|
|
8
|
-
await client.zadd('key', '' + Date.now(), '111')
|
|
9
|
-
expect(await client.zcard('key')).to.be.eql(1)
|
|
10
|
-
await release(client, 'key', '111')
|
|
11
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
12
|
-
})
|
|
13
|
-
it('should do nothing if resource is not locked', async () => {
|
|
14
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
15
|
-
await release(client, 'key', '111')
|
|
16
|
-
expect(await client.zcard('key')).to.be.eql(0)
|
|
17
|
-
})
|
|
18
|
-
})
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { createEval } from '../../../src/utils/index'
|
|
4
|
-
import { client1 as client } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('utils createEval', () => {
|
|
7
|
-
it('should return function', async () => {
|
|
8
|
-
expect(createEval('return 5', 0)).to.be.a('function')
|
|
9
|
-
})
|
|
10
|
-
it('should call evalsha or fallback to eval', async () => {
|
|
11
|
-
const now = Date.now()
|
|
12
|
-
const SCRIPT = `return ${now}`
|
|
13
|
-
const execScript = createEval(SCRIPT, 0)
|
|
14
|
-
const result = await execScript(client, [])
|
|
15
|
-
expect(result).to.be.eql(now)
|
|
16
|
-
expect(Date.now() - now).to.be.lt(50)
|
|
17
|
-
})
|
|
18
|
-
it('should handle eval errors', async () => {
|
|
19
|
-
const execScript = createEval('return asdfkasjdf', 0)
|
|
20
|
-
await expect(execScript(client, [])).to.be.rejected
|
|
21
|
-
})
|
|
22
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
import Redis from 'ioredis'
|
|
3
|
-
import { getConnectionName } from '../../../src/utils/index'
|
|
4
|
-
import { client1 } from '../../redisClient'
|
|
5
|
-
|
|
6
|
-
describe('utils getConnectionName', () => {
|
|
7
|
-
it('should return connection name', async () => {
|
|
8
|
-
expect(getConnectionName(client1)).to.be.eql('<client1>')
|
|
9
|
-
})
|
|
10
|
-
it('should return unknown if connection name not configured', () => {
|
|
11
|
-
const client = new Redis('redis://127.0.0.1:6000', {
|
|
12
|
-
lazyConnect: true,
|
|
13
|
-
enableOfflineQueue: false,
|
|
14
|
-
autoResendUnfulfilledCommands: false, // dont queue commands while server is offline (dont break test logic)
|
|
15
|
-
maxRetriesPerRequest: 0 // dont retry, fail faster (default is 20)
|
|
16
|
-
})
|
|
17
|
-
expect(getConnectionName(client)).to.be.eql('<unknown client>')
|
|
18
|
-
})
|
|
19
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
|
|
3
|
-
import { getQuorum } from '../../../src/utils/redlock'
|
|
4
|
-
|
|
5
|
-
describe('redlockMutex utils', () => {
|
|
6
|
-
describe('getQuorum', () => {
|
|
7
|
-
function makeTest(count: number, expectedResult: number) {
|
|
8
|
-
it(`should return valid majority for ${count} nodes`, () => {
|
|
9
|
-
expect(getQuorum(count)).to.be.eql(expectedResult)
|
|
10
|
-
expect(getQuorum(2)).to.be.eql(2)
|
|
11
|
-
expect(getQuorum(3)).to.be.eql(2)
|
|
12
|
-
expect(getQuorum(4)).to.be.eql(3)
|
|
13
|
-
expect(getQuorum(5)).to.be.eql(3)
|
|
14
|
-
expect(getQuorum(6)).to.be.eql(4)
|
|
15
|
-
expect(getQuorum(7)).to.be.eql(4)
|
|
16
|
-
expect(getQuorum(8)).to.be.eql(5)
|
|
17
|
-
expect(getQuorum(9)).to.be.eql(5)
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
// makeTest(0, 1)
|
|
21
|
-
makeTest(1, 1)
|
|
22
|
-
makeTest(2, 2)
|
|
23
|
-
makeTest(3, 2)
|
|
24
|
-
makeTest(4, 3)
|
|
25
|
-
makeTest(5, 3)
|
|
26
|
-
makeTest(6, 4)
|
|
27
|
-
makeTest(7, 4)
|
|
28
|
-
makeTest(8, 5)
|
|
29
|
-
makeTest(9, 5)
|
|
30
|
-
})
|
|
31
|
-
})
|