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,22 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../../types'
|
|
3
|
-
import { releaseLua } from './lua'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:multi-semaphore:release')
|
|
6
|
-
|
|
7
|
-
export interface Options {
|
|
8
|
-
identifier: string
|
|
9
|
-
lockTimeout: number
|
|
10
|
-
now: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function releaseSemaphore(
|
|
14
|
-
client: RedisClient,
|
|
15
|
-
key: string,
|
|
16
|
-
permits: number,
|
|
17
|
-
identifier: string
|
|
18
|
-
): Promise<void> {
|
|
19
|
-
debug(key, identifier, permits)
|
|
20
|
-
const result = await releaseLua(client, [key, permits, identifier])
|
|
21
|
-
debug('result', typeof result, result)
|
|
22
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { createEval } from '../../utils/index'
|
|
2
|
-
|
|
3
|
-
export const releaseLua = createEval<[string, number, string], number>(
|
|
4
|
-
`
|
|
5
|
-
local key = KEYS[1]
|
|
6
|
-
local permits = tonumber(ARGV[1])
|
|
7
|
-
local identifier = ARGV[2]
|
|
8
|
-
local args = {}
|
|
9
|
-
|
|
10
|
-
for i=0, permits - 1 do
|
|
11
|
-
table.insert(args, identifier .. '_' .. i)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
return redis.call('zrem', key, unpack(args))
|
|
15
|
-
`,
|
|
16
|
-
1
|
|
17
|
-
)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../types'
|
|
3
|
-
import { delay } from '../utils'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:mutex:acquire')
|
|
6
|
-
|
|
7
|
-
export interface Options {
|
|
8
|
-
identifier: string
|
|
9
|
-
lockTimeout: number
|
|
10
|
-
acquireTimeout: number
|
|
11
|
-
acquireAttemptsLimit: number
|
|
12
|
-
retryInterval: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function acquireMutex(
|
|
16
|
-
client: RedisClient,
|
|
17
|
-
key: string,
|
|
18
|
-
options: Options
|
|
19
|
-
): Promise<boolean> {
|
|
20
|
-
const {
|
|
21
|
-
identifier,
|
|
22
|
-
lockTimeout,
|
|
23
|
-
acquireTimeout,
|
|
24
|
-
acquireAttemptsLimit,
|
|
25
|
-
retryInterval
|
|
26
|
-
} = options
|
|
27
|
-
let attempt = 0
|
|
28
|
-
const end = Date.now() + acquireTimeout
|
|
29
|
-
while (Date.now() < end && ++attempt <= acquireAttemptsLimit) {
|
|
30
|
-
debug(key, identifier, 'attempt', attempt)
|
|
31
|
-
const result = await client.set(key, identifier, 'PX', lockTimeout, 'NX')
|
|
32
|
-
debug('result', typeof result, result)
|
|
33
|
-
if (result === 'OK') {
|
|
34
|
-
debug(key, identifier, 'acquired')
|
|
35
|
-
return true
|
|
36
|
-
} else {
|
|
37
|
-
await delay(retryInterval)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
debug(key, identifier, 'timeout or reach limit')
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../types'
|
|
3
|
-
import { createEval } from '../utils/index'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:mutex:refresh')
|
|
6
|
-
|
|
7
|
-
export const expireIfEqualLua = createEval<[string, string, number], 0 | 1>(
|
|
8
|
-
`
|
|
9
|
-
local key = KEYS[1]
|
|
10
|
-
local identifier = ARGV[1]
|
|
11
|
-
local lockTimeout = ARGV[2]
|
|
12
|
-
|
|
13
|
-
local value = redis.call('get', key)
|
|
14
|
-
|
|
15
|
-
if value == identifier then
|
|
16
|
-
redis.call('pexpire', key, lockTimeout)
|
|
17
|
-
return 1
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
return 0
|
|
21
|
-
`,
|
|
22
|
-
1
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
export async function refreshMutex(
|
|
26
|
-
client: RedisClient,
|
|
27
|
-
key: string,
|
|
28
|
-
identifier: string,
|
|
29
|
-
lockTimeout: number
|
|
30
|
-
): Promise<boolean> {
|
|
31
|
-
debug(key, identifier)
|
|
32
|
-
const result = await expireIfEqualLua(client, [key, identifier, lockTimeout])
|
|
33
|
-
debug('result', typeof result, result)
|
|
34
|
-
|
|
35
|
-
// support options.stringNumbers
|
|
36
|
-
return +result === 1
|
|
37
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { createEval } from '../utils/index'
|
|
3
|
-
|
|
4
|
-
import type { RedisClient } from '../types'
|
|
5
|
-
|
|
6
|
-
const debug = createDebug('redis-semaphore:mutex:release')
|
|
7
|
-
|
|
8
|
-
export const delIfEqualLua = createEval<[string, string], 0 | 1>(
|
|
9
|
-
`
|
|
10
|
-
local key = KEYS[1]
|
|
11
|
-
local identifier = ARGV[1]
|
|
12
|
-
|
|
13
|
-
if redis.call('get', key) == identifier then
|
|
14
|
-
return redis.call('del', key)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
return 0
|
|
18
|
-
`,
|
|
19
|
-
1
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
export async function releaseMutex(
|
|
23
|
-
client: RedisClient,
|
|
24
|
-
key: string,
|
|
25
|
-
identifier: string
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
debug(key, identifier)
|
|
28
|
-
const result = await delIfEqualLua(client, [key, identifier])
|
|
29
|
-
debug('result', typeof result, result)
|
|
30
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { acquireLua } from '../multiSemaphore/acquire/lua'
|
|
3
|
-
import { RedisClient } from '../types'
|
|
4
|
-
import { delay } from '../utils'
|
|
5
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
6
|
-
|
|
7
|
-
const debug = createDebug('redis-semaphore:redlock-multi-semaphore:acquire')
|
|
8
|
-
|
|
9
|
-
export interface Options {
|
|
10
|
-
identifier: string
|
|
11
|
-
lockTimeout: number
|
|
12
|
-
acquireTimeout: number
|
|
13
|
-
acquireAttemptsLimit: number
|
|
14
|
-
retryInterval: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function acquireRedlockMultiSemaphore(
|
|
18
|
-
clients: RedisClient[],
|
|
19
|
-
key: string,
|
|
20
|
-
limit: number,
|
|
21
|
-
permits: number,
|
|
22
|
-
options: Options
|
|
23
|
-
): Promise<boolean> {
|
|
24
|
-
const {
|
|
25
|
-
identifier,
|
|
26
|
-
lockTimeout,
|
|
27
|
-
acquireTimeout,
|
|
28
|
-
acquireAttemptsLimit,
|
|
29
|
-
retryInterval
|
|
30
|
-
} = options
|
|
31
|
-
let attempt = 0
|
|
32
|
-
const end = Date.now() + acquireTimeout
|
|
33
|
-
const quorum = getQuorum(clients.length)
|
|
34
|
-
let now: number
|
|
35
|
-
while ((now = Date.now()) < end && ++attempt <= acquireAttemptsLimit) {
|
|
36
|
-
debug(key, identifier, 'attempt', attempt)
|
|
37
|
-
const promises = clients.map(client =>
|
|
38
|
-
acquireLua(client, [key, limit, permits, identifier, lockTimeout, now])
|
|
39
|
-
.then(result => +result)
|
|
40
|
-
.catch(() => 0)
|
|
41
|
-
)
|
|
42
|
-
const results = await Promise.all(promises)
|
|
43
|
-
if (results.reduce(smartSum, 0) >= quorum) {
|
|
44
|
-
debug(key, identifier, 'acquired')
|
|
45
|
-
return true
|
|
46
|
-
} else {
|
|
47
|
-
const promises = clients.map(client =>
|
|
48
|
-
client.zrem(key, identifier).catch(() => 0)
|
|
49
|
-
)
|
|
50
|
-
await Promise.all(promises)
|
|
51
|
-
await delay(retryInterval)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
debug(key, identifier, 'timeout or reach limit')
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { acquireLua } from '../multiSemaphore/acquire/lua'
|
|
3
|
-
import { refreshLua } from '../multiSemaphore/refresh/lua'
|
|
4
|
-
import { releaseLua } from '../multiSemaphore/release/lua'
|
|
5
|
-
import { RedisClient } from '../types'
|
|
6
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
7
|
-
|
|
8
|
-
const debug = createDebug('redis-semaphore:redlock-semaphore:refresh')
|
|
9
|
-
|
|
10
|
-
interface Options {
|
|
11
|
-
identifier: string
|
|
12
|
-
lockTimeout: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function refreshRedlockMultiSemaphore(
|
|
16
|
-
clients: RedisClient[],
|
|
17
|
-
key: string,
|
|
18
|
-
limit: number,
|
|
19
|
-
permits: number,
|
|
20
|
-
options: Options
|
|
21
|
-
): Promise<boolean> {
|
|
22
|
-
const { identifier, lockTimeout } = options
|
|
23
|
-
const now = Date.now()
|
|
24
|
-
debug(key, identifier, now)
|
|
25
|
-
const quorum = getQuorum(clients.length)
|
|
26
|
-
const promises = clients.map(client =>
|
|
27
|
-
refreshLua(client, [key, limit, permits, identifier, lockTimeout, now])
|
|
28
|
-
.then(result => +result)
|
|
29
|
-
.catch(() => 0)
|
|
30
|
-
)
|
|
31
|
-
const results = await Promise.all(promises)
|
|
32
|
-
debug('results', results)
|
|
33
|
-
const refreshedCount = results.reduce(smartSum, 0)
|
|
34
|
-
if (refreshedCount >= quorum) {
|
|
35
|
-
debug(key, identifier, 'refreshed')
|
|
36
|
-
if (refreshedCount < clients.length) {
|
|
37
|
-
debug(key, identifier, 'try to acquire on failed nodes')
|
|
38
|
-
const promises = results
|
|
39
|
-
.reduce<RedisClient[]>((failedClients, result, index) => {
|
|
40
|
-
if (!result) {
|
|
41
|
-
failedClients.push(clients[index])
|
|
42
|
-
}
|
|
43
|
-
return failedClients
|
|
44
|
-
}, [])
|
|
45
|
-
.map(client =>
|
|
46
|
-
acquireLua(client, [
|
|
47
|
-
key,
|
|
48
|
-
limit,
|
|
49
|
-
permits,
|
|
50
|
-
identifier,
|
|
51
|
-
lockTimeout,
|
|
52
|
-
now
|
|
53
|
-
])
|
|
54
|
-
.then(result => +result)
|
|
55
|
-
.catch(() => 0)
|
|
56
|
-
)
|
|
57
|
-
const acquireResults = await Promise.all(promises)
|
|
58
|
-
debug(key, identifier, 'acquire on failed nodes results', acquireResults)
|
|
59
|
-
}
|
|
60
|
-
return true
|
|
61
|
-
} else {
|
|
62
|
-
const promises = clients.map(client =>
|
|
63
|
-
releaseLua(client, [key, permits, identifier]).catch(() => 0)
|
|
64
|
-
)
|
|
65
|
-
await Promise.all(promises)
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { releaseLua } from '../multiSemaphore/release/lua'
|
|
3
|
-
import { RedisClient } from '../types'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:redlock-mutex:release')
|
|
6
|
-
|
|
7
|
-
export async function releaseRedlockMultiSemaphore(
|
|
8
|
-
clients: RedisClient[],
|
|
9
|
-
key: string,
|
|
10
|
-
permits: number,
|
|
11
|
-
identifier: string
|
|
12
|
-
): Promise<void> {
|
|
13
|
-
debug(key, identifier)
|
|
14
|
-
const promises = clients.map(client =>
|
|
15
|
-
releaseLua(client, [key, permits, identifier]).catch(() => 0)
|
|
16
|
-
)
|
|
17
|
-
const results = await Promise.all(promises)
|
|
18
|
-
debug('results', results)
|
|
19
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { delIfEqualLua } from '../mutex/release'
|
|
3
|
-
import { RedisClient } from '../types'
|
|
4
|
-
import { delay } from '../utils'
|
|
5
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
6
|
-
|
|
7
|
-
const debug = createDebug('redis-semaphore:redlock-mutex:acquire')
|
|
8
|
-
|
|
9
|
-
export interface Options {
|
|
10
|
-
identifier: string
|
|
11
|
-
lockTimeout: number
|
|
12
|
-
acquireTimeout: number
|
|
13
|
-
acquireAttemptsLimit: number
|
|
14
|
-
retryInterval: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function acquireRedlockMutex(
|
|
18
|
-
clients: RedisClient[],
|
|
19
|
-
key: string,
|
|
20
|
-
options: Options
|
|
21
|
-
): Promise<boolean> {
|
|
22
|
-
const {
|
|
23
|
-
identifier,
|
|
24
|
-
lockTimeout,
|
|
25
|
-
acquireTimeout,
|
|
26
|
-
acquireAttemptsLimit,
|
|
27
|
-
retryInterval
|
|
28
|
-
} = options
|
|
29
|
-
let attempt = 0
|
|
30
|
-
const end = Date.now() + acquireTimeout
|
|
31
|
-
const quorum = getQuorum(clients.length)
|
|
32
|
-
while (Date.now() < end && ++attempt <= acquireAttemptsLimit) {
|
|
33
|
-
debug(key, identifier, 'attempt', attempt)
|
|
34
|
-
const promises = clients.map(client =>
|
|
35
|
-
client
|
|
36
|
-
.set(key, identifier, 'PX', lockTimeout, 'NX')
|
|
37
|
-
.then(result => (result === 'OK' ? 1 : 0))
|
|
38
|
-
.catch(() => 0)
|
|
39
|
-
)
|
|
40
|
-
const results = await Promise.all(promises)
|
|
41
|
-
if (results.reduce(smartSum, 0) >= quorum) {
|
|
42
|
-
debug(key, identifier, 'acquired')
|
|
43
|
-
return true
|
|
44
|
-
} else {
|
|
45
|
-
const promises = clients.map(client =>
|
|
46
|
-
delIfEqualLua(client, [key, identifier]).catch(() => 0)
|
|
47
|
-
)
|
|
48
|
-
await Promise.all(promises)
|
|
49
|
-
await delay(retryInterval)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
debug(key, identifier, 'timeout or reach limit')
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { expireIfEqualLua } from '../mutex/refresh'
|
|
3
|
-
import { delIfEqualLua } from '../mutex/release'
|
|
4
|
-
import { RedisClient } from '../types'
|
|
5
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
6
|
-
|
|
7
|
-
const debug = createDebug('redis-semaphore:redlock-mutex:refresh')
|
|
8
|
-
|
|
9
|
-
export async function refreshRedlockMutex(
|
|
10
|
-
clients: RedisClient[],
|
|
11
|
-
key: string,
|
|
12
|
-
identifier: string,
|
|
13
|
-
lockTimeout: number
|
|
14
|
-
): Promise<boolean> {
|
|
15
|
-
debug(key, identifier)
|
|
16
|
-
const quorum = getQuorum(clients.length)
|
|
17
|
-
const promises = clients.map(client =>
|
|
18
|
-
expireIfEqualLua(client, [key, identifier, lockTimeout])
|
|
19
|
-
.then(result => +result)
|
|
20
|
-
.catch(() => 0)
|
|
21
|
-
)
|
|
22
|
-
const results = await Promise.all(promises)
|
|
23
|
-
debug('results', results)
|
|
24
|
-
const refreshedCount = results.reduce(smartSum, 0)
|
|
25
|
-
if (refreshedCount >= quorum) {
|
|
26
|
-
debug(key, identifier, 'refreshed')
|
|
27
|
-
if (refreshedCount < clients.length) {
|
|
28
|
-
debug(key, identifier, 'try to acquire on failed nodes')
|
|
29
|
-
const promises = results
|
|
30
|
-
.reduce<RedisClient[]>((failedClients, result, index) => {
|
|
31
|
-
if (!result) {
|
|
32
|
-
failedClients.push(clients[index])
|
|
33
|
-
}
|
|
34
|
-
return failedClients
|
|
35
|
-
}, [])
|
|
36
|
-
.map(client =>
|
|
37
|
-
client
|
|
38
|
-
.set(key, identifier, 'PX', lockTimeout, 'NX')
|
|
39
|
-
.then(result => (result === 'OK' ? 1 : 0))
|
|
40
|
-
.catch(() => 0)
|
|
41
|
-
)
|
|
42
|
-
const acquireResults = await Promise.all(promises)
|
|
43
|
-
debug(key, identifier, 'acquire on failed nodes results', acquireResults)
|
|
44
|
-
}
|
|
45
|
-
return true
|
|
46
|
-
} else {
|
|
47
|
-
const promises = clients.map(client =>
|
|
48
|
-
delIfEqualLua(client, [key, identifier]).catch(() => 0)
|
|
49
|
-
)
|
|
50
|
-
await Promise.all(promises)
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { delIfEqualLua } from '../mutex/release'
|
|
3
|
-
|
|
4
|
-
import type { RedisClient } from '../types'
|
|
5
|
-
|
|
6
|
-
const debug = createDebug('redis-semaphore:redlock-mutex:release')
|
|
7
|
-
|
|
8
|
-
export async function releaseRedlockMutex(
|
|
9
|
-
clients: RedisClient[],
|
|
10
|
-
key: string,
|
|
11
|
-
identifier: string
|
|
12
|
-
): Promise<void> {
|
|
13
|
-
debug(key, identifier)
|
|
14
|
-
const promises = clients.map(client =>
|
|
15
|
-
delIfEqualLua(client, [key, identifier]).catch(() => 0)
|
|
16
|
-
)
|
|
17
|
-
const results = await Promise.all(promises)
|
|
18
|
-
debug('results', results)
|
|
19
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { acquireLua } from '../semaphore/acquire/lua'
|
|
3
|
-
import { RedisClient } from '../types'
|
|
4
|
-
import { delay } from '../utils'
|
|
5
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
6
|
-
|
|
7
|
-
const debug = createDebug('redis-semaphore:redlock-semaphore:acquire')
|
|
8
|
-
|
|
9
|
-
export interface Options {
|
|
10
|
-
identifier: string
|
|
11
|
-
lockTimeout: number
|
|
12
|
-
acquireTimeout: number
|
|
13
|
-
acquireAttemptsLimit: number
|
|
14
|
-
retryInterval: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function acquireRedlockSemaphore(
|
|
18
|
-
clients: RedisClient[],
|
|
19
|
-
key: string,
|
|
20
|
-
limit: number,
|
|
21
|
-
options: Options
|
|
22
|
-
): Promise<boolean> {
|
|
23
|
-
const {
|
|
24
|
-
identifier,
|
|
25
|
-
lockTimeout,
|
|
26
|
-
acquireTimeout,
|
|
27
|
-
acquireAttemptsLimit,
|
|
28
|
-
retryInterval
|
|
29
|
-
} = options
|
|
30
|
-
let attempt = 0
|
|
31
|
-
const end = Date.now() + acquireTimeout
|
|
32
|
-
const quorum = getQuorum(clients.length)
|
|
33
|
-
let now: number
|
|
34
|
-
while ((now = Date.now()) < end && ++attempt <= acquireAttemptsLimit) {
|
|
35
|
-
debug(key, identifier, 'attempt', attempt)
|
|
36
|
-
const promises = clients.map(client =>
|
|
37
|
-
acquireLua(client, [key, limit, identifier, lockTimeout, now])
|
|
38
|
-
.then(result => +result)
|
|
39
|
-
.catch(() => 0)
|
|
40
|
-
)
|
|
41
|
-
const results = await Promise.all(promises)
|
|
42
|
-
if (results.reduce(smartSum, 0) >= quorum) {
|
|
43
|
-
debug(key, identifier, 'acquired')
|
|
44
|
-
return true
|
|
45
|
-
} else {
|
|
46
|
-
const promises = clients.map(client =>
|
|
47
|
-
client.zrem(key, identifier).catch(() => 0)
|
|
48
|
-
)
|
|
49
|
-
await Promise.all(promises)
|
|
50
|
-
await delay(retryInterval)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
debug(key, identifier, 'timeout or reach limit')
|
|
54
|
-
return false
|
|
55
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { acquireLua } from '../semaphore/acquire/lua'
|
|
3
|
-
import { refreshLua } from '../semaphore/refresh/lua'
|
|
4
|
-
import { getQuorum, smartSum } from '../utils/redlock'
|
|
5
|
-
|
|
6
|
-
import type { RedisClient } from '../types'
|
|
7
|
-
|
|
8
|
-
const debug = createDebug('redis-semaphore:redlock-semaphore:refresh')
|
|
9
|
-
|
|
10
|
-
interface Options {
|
|
11
|
-
identifier: string
|
|
12
|
-
lockTimeout: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function refreshRedlockSemaphore(
|
|
16
|
-
clients: RedisClient[],
|
|
17
|
-
key: string,
|
|
18
|
-
limit: number,
|
|
19
|
-
options: Options
|
|
20
|
-
): Promise<boolean> {
|
|
21
|
-
const { identifier, lockTimeout } = options
|
|
22
|
-
const now = Date.now()
|
|
23
|
-
debug(key, identifier, now)
|
|
24
|
-
const quorum = getQuorum(clients.length)
|
|
25
|
-
const promises = clients.map(client =>
|
|
26
|
-
refreshLua(client, [key, limit, identifier, lockTimeout, now])
|
|
27
|
-
.then(result => +result)
|
|
28
|
-
.catch(() => 0)
|
|
29
|
-
)
|
|
30
|
-
const results = await Promise.all(promises)
|
|
31
|
-
debug('results', results)
|
|
32
|
-
const refreshedCount = results.reduce(smartSum, 0)
|
|
33
|
-
if (refreshedCount >= quorum) {
|
|
34
|
-
debug(key, identifier, 'refreshed')
|
|
35
|
-
if (refreshedCount < clients.length) {
|
|
36
|
-
debug(key, identifier, 'try to acquire on failed nodes')
|
|
37
|
-
const promises = results
|
|
38
|
-
.reduce<RedisClient[]>((failedClients, result, index) => {
|
|
39
|
-
if (!result) {
|
|
40
|
-
failedClients.push(clients[index])
|
|
41
|
-
}
|
|
42
|
-
return failedClients
|
|
43
|
-
}, [])
|
|
44
|
-
.map(client =>
|
|
45
|
-
acquireLua(client, [key, limit, identifier, lockTimeout, now])
|
|
46
|
-
.then(result => +result)
|
|
47
|
-
.catch(() => 0)
|
|
48
|
-
)
|
|
49
|
-
const acquireResults = await Promise.all(promises)
|
|
50
|
-
debug(key, identifier, 'acquire on failed nodes results', acquireResults)
|
|
51
|
-
}
|
|
52
|
-
return true
|
|
53
|
-
} else {
|
|
54
|
-
const promises = clients.map(client =>
|
|
55
|
-
client.zrem(key, identifier).catch(() => 0)
|
|
56
|
-
)
|
|
57
|
-
await Promise.all(promises)
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
|
|
3
|
-
import type { RedisClient } from '../types'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:redlock-mutex:release')
|
|
6
|
-
|
|
7
|
-
export async function releaseRedlockSemaphore(
|
|
8
|
-
clients: RedisClient[],
|
|
9
|
-
key: string,
|
|
10
|
-
identifier: string
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
debug(key, identifier)
|
|
13
|
-
const promises = clients.map(client =>
|
|
14
|
-
client.zrem(key, identifier).catch(() => 0)
|
|
15
|
-
)
|
|
16
|
-
const results = await Promise.all(promises)
|
|
17
|
-
debug('results', results)
|
|
18
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../../types'
|
|
3
|
-
import { delay } from '../../utils'
|
|
4
|
-
import { acquireLua } from './lua'
|
|
5
|
-
|
|
6
|
-
const debug = createDebug('redis-semaphore:semaphore:acquire')
|
|
7
|
-
|
|
8
|
-
export interface Options {
|
|
9
|
-
identifier: string
|
|
10
|
-
lockTimeout: number
|
|
11
|
-
acquireTimeout: number
|
|
12
|
-
acquireAttemptsLimit: number
|
|
13
|
-
retryInterval: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function acquireSemaphore(
|
|
17
|
-
client: RedisClient,
|
|
18
|
-
key: string,
|
|
19
|
-
limit: number,
|
|
20
|
-
options: Options
|
|
21
|
-
): Promise<boolean> {
|
|
22
|
-
const {
|
|
23
|
-
identifier,
|
|
24
|
-
lockTimeout,
|
|
25
|
-
acquireTimeout,
|
|
26
|
-
acquireAttemptsLimit,
|
|
27
|
-
retryInterval
|
|
28
|
-
} = options
|
|
29
|
-
let attempt = 0
|
|
30
|
-
const end = Date.now() + acquireTimeout
|
|
31
|
-
let now
|
|
32
|
-
while ((now = Date.now()) < end && ++attempt <= acquireAttemptsLimit) {
|
|
33
|
-
debug(key, identifier, limit, lockTimeout, 'attempt', attempt)
|
|
34
|
-
const result = await acquireLua(client, [
|
|
35
|
-
key,
|
|
36
|
-
limit,
|
|
37
|
-
identifier,
|
|
38
|
-
lockTimeout,
|
|
39
|
-
now
|
|
40
|
-
])
|
|
41
|
-
debug(key, 'result', typeof result, result)
|
|
42
|
-
// support options.stringNumbers
|
|
43
|
-
if (+result === 1) {
|
|
44
|
-
debug(key, identifier, 'acquired')
|
|
45
|
-
return true
|
|
46
|
-
} else {
|
|
47
|
-
await delay(retryInterval)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
debug(key, identifier, limit, lockTimeout, 'timeout or reach limit')
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { createEval } from '../../utils/index'
|
|
2
|
-
|
|
3
|
-
export const acquireLua = createEval<
|
|
4
|
-
[string, number, string, number, number],
|
|
5
|
-
0 | 1
|
|
6
|
-
>(
|
|
7
|
-
`
|
|
8
|
-
local key = KEYS[1]
|
|
9
|
-
local limit = tonumber(ARGV[1])
|
|
10
|
-
local identifier = ARGV[2]
|
|
11
|
-
local lockTimeout = tonumber(ARGV[3])
|
|
12
|
-
local now = tonumber(ARGV[4])
|
|
13
|
-
local expiredTimestamp = now - lockTimeout
|
|
14
|
-
|
|
15
|
-
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
16
|
-
|
|
17
|
-
if redis.call('zcard', key) < limit then
|
|
18
|
-
redis.call('zadd', key, now, identifier)
|
|
19
|
-
redis.call('pexpire', key, lockTimeout)
|
|
20
|
-
return 1
|
|
21
|
-
else
|
|
22
|
-
return 0
|
|
23
|
-
end`,
|
|
24
|
-
1
|
|
25
|
-
)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../../types'
|
|
3
|
-
import { refreshLua } from './lua'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:semaphore:refresh')
|
|
6
|
-
|
|
7
|
-
export interface Options {
|
|
8
|
-
identifier: string
|
|
9
|
-
lockTimeout: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function refreshSemaphore(
|
|
13
|
-
client: RedisClient,
|
|
14
|
-
key: string,
|
|
15
|
-
limit: number,
|
|
16
|
-
options: Options
|
|
17
|
-
): Promise<boolean> {
|
|
18
|
-
const { identifier, lockTimeout } = options
|
|
19
|
-
const now = Date.now()
|
|
20
|
-
debug(key, identifier, now)
|
|
21
|
-
const result = await refreshLua(client, [
|
|
22
|
-
key,
|
|
23
|
-
limit,
|
|
24
|
-
identifier,
|
|
25
|
-
lockTimeout,
|
|
26
|
-
now
|
|
27
|
-
])
|
|
28
|
-
debug('result', typeof result, result)
|
|
29
|
-
// support options.stringNumbers
|
|
30
|
-
return +result === 1
|
|
31
|
-
}
|