effect-distributed-lock 0.0.6 → 0.0.7
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/concurrent.ts +111 -0
- package/package.json +1 -1
- package/src/DistributedSemaphore.ts +1 -0
- package/src/RedisBacking.ts +2 -2
- 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,172 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import * as crypto from 'node:crypto'
|
|
3
|
-
import LostLockError from './errors/LostLockError'
|
|
4
|
-
import TimeoutError from './errors/TimeoutError'
|
|
5
|
-
import { defaultOnLockLost, defaultTimeoutOptions } from './misc'
|
|
6
|
-
import { AcquireOptions, LockLostCallback, LockOptions } from './types'
|
|
7
|
-
|
|
8
|
-
const REFRESH_INTERVAL_COEF = 0.8
|
|
9
|
-
|
|
10
|
-
const debug = createDebug('redis-semaphore:instance')
|
|
11
|
-
|
|
12
|
-
export abstract class Lock {
|
|
13
|
-
protected abstract _kind: string
|
|
14
|
-
protected abstract _key: string
|
|
15
|
-
protected _identifier: string
|
|
16
|
-
protected _acquireOptions: AcquireOptions
|
|
17
|
-
protected _refreshTimeInterval: number
|
|
18
|
-
protected _refreshInterval?: ReturnType<typeof setInterval>
|
|
19
|
-
protected _refreshing = false
|
|
20
|
-
protected _acquired = false
|
|
21
|
-
protected _acquiredExternally = false
|
|
22
|
-
protected _onLockLost: LockLostCallback
|
|
23
|
-
|
|
24
|
-
protected abstract _refresh(): Promise<boolean>
|
|
25
|
-
protected abstract _acquire(): Promise<boolean>
|
|
26
|
-
protected abstract _release(): Promise<void>
|
|
27
|
-
|
|
28
|
-
constructor({
|
|
29
|
-
lockTimeout = defaultTimeoutOptions.lockTimeout,
|
|
30
|
-
acquireTimeout = defaultTimeoutOptions.acquireTimeout,
|
|
31
|
-
acquireAttemptsLimit = defaultTimeoutOptions.acquireAttemptsLimit,
|
|
32
|
-
retryInterval = defaultTimeoutOptions.retryInterval,
|
|
33
|
-
refreshInterval = Math.round(lockTimeout * REFRESH_INTERVAL_COEF),
|
|
34
|
-
onLockLost = defaultOnLockLost,
|
|
35
|
-
externallyAcquiredIdentifier,
|
|
36
|
-
identifierSuffix,
|
|
37
|
-
identifier,
|
|
38
|
-
acquiredExternally
|
|
39
|
-
}: LockOptions = defaultTimeoutOptions) {
|
|
40
|
-
if (
|
|
41
|
-
identifier !== undefined &&
|
|
42
|
-
(!identifier || typeof identifier !== 'string')
|
|
43
|
-
) {
|
|
44
|
-
throw new Error('identifier must be not empty random string')
|
|
45
|
-
}
|
|
46
|
-
if (acquiredExternally && !identifier) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
'acquiredExternally=true meanless without custom identifier'
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
if (externallyAcquiredIdentifier && (identifier || acquiredExternally)) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'Invalid usage. Use custom identifier and acquiredExternally: true'
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
this._identifier =
|
|
57
|
-
identifier ||
|
|
58
|
-
externallyAcquiredIdentifier ||
|
|
59
|
-
this.getIdentifier(identifierSuffix)
|
|
60
|
-
this._acquiredExternally =
|
|
61
|
-
!!acquiredExternally || !!externallyAcquiredIdentifier
|
|
62
|
-
this._acquireOptions = {
|
|
63
|
-
lockTimeout,
|
|
64
|
-
acquireTimeout,
|
|
65
|
-
acquireAttemptsLimit,
|
|
66
|
-
retryInterval,
|
|
67
|
-
identifier: this._identifier
|
|
68
|
-
}
|
|
69
|
-
this._refreshTimeInterval = refreshInterval
|
|
70
|
-
this._onLockLost = onLockLost
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get identifier(): string {
|
|
74
|
-
return this._identifier
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get isAcquired(): boolean {
|
|
78
|
-
return this._acquired
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private getIdentifier(identifierSuffix: string | undefined): string {
|
|
82
|
-
const uuid = crypto.randomUUID()
|
|
83
|
-
return identifierSuffix ? `${uuid}-${identifierSuffix}` : uuid
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private _startRefresh(): void {
|
|
87
|
-
this._refreshInterval = setInterval(
|
|
88
|
-
this._processRefresh,
|
|
89
|
-
this._refreshTimeInterval
|
|
90
|
-
)
|
|
91
|
-
this._refreshInterval.unref()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
stopRefresh(): void {
|
|
95
|
-
if (this._refreshInterval) {
|
|
96
|
-
debug(
|
|
97
|
-
`clear refresh interval ${this._kind} (key: ${this._key}, identifier: ${this._identifier})`
|
|
98
|
-
)
|
|
99
|
-
clearInterval(this._refreshInterval)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private _processRefresh = async (): Promise<void> => {
|
|
104
|
-
if (this._refreshing) {
|
|
105
|
-
debug(
|
|
106
|
-
`already refreshing ${this._kind} (key: ${this._key}, identifier: ${this._identifier}) (skip)`
|
|
107
|
-
)
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
this._refreshing = true
|
|
111
|
-
try {
|
|
112
|
-
debug(
|
|
113
|
-
`refresh ${this._kind} (key: ${this._key}, identifier: ${this._identifier})`
|
|
114
|
-
)
|
|
115
|
-
const refreshed = await this._refresh()
|
|
116
|
-
if (!refreshed) {
|
|
117
|
-
if (!this._acquired) {
|
|
118
|
-
debug(
|
|
119
|
-
`refresh ${this._kind} (key: ${this._key}, identifier: ${this._identifier} failed, but lock was purposefully released`
|
|
120
|
-
)
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
this._acquired = false
|
|
124
|
-
this.stopRefresh()
|
|
125
|
-
const lockLostError = new LostLockError(
|
|
126
|
-
`Lost ${this._kind} for key ${this._key}`
|
|
127
|
-
)
|
|
128
|
-
this._onLockLost(lockLostError)
|
|
129
|
-
}
|
|
130
|
-
} finally {
|
|
131
|
-
this._refreshing = false
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async acquire(): Promise<void> {
|
|
136
|
-
debug(`acquire ${this._kind} (key: ${this._key})`)
|
|
137
|
-
const acquired = await this.tryAcquire()
|
|
138
|
-
if (!acquired) {
|
|
139
|
-
throw new TimeoutError(`Acquire ${this._kind} ${this._key} timeout`)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async tryAcquire(): Promise<boolean> {
|
|
144
|
-
debug(`tryAcquire ${this._kind} (key: ${this._key})`)
|
|
145
|
-
const acquired = this._acquiredExternally
|
|
146
|
-
? await this._refresh()
|
|
147
|
-
: await this._acquire()
|
|
148
|
-
if (!acquired) {
|
|
149
|
-
return false
|
|
150
|
-
}
|
|
151
|
-
this._acquired = true
|
|
152
|
-
this._acquiredExternally = false
|
|
153
|
-
if (this._refreshTimeInterval > 0) {
|
|
154
|
-
this._startRefresh()
|
|
155
|
-
}
|
|
156
|
-
return true
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async release(): Promise<void> {
|
|
160
|
-
debug(
|
|
161
|
-
`release ${this._kind} (key: ${this._key}, identifier: ${this._identifier})`
|
|
162
|
-
)
|
|
163
|
-
if (this._refreshTimeInterval > 0) {
|
|
164
|
-
this.stopRefresh()
|
|
165
|
-
}
|
|
166
|
-
if (this._acquired || this._acquiredExternally) {
|
|
167
|
-
await this._release()
|
|
168
|
-
}
|
|
169
|
-
this._acquired = false
|
|
170
|
-
this._acquiredExternally = false
|
|
171
|
-
}
|
|
172
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { acquireSemaphore } from './multiSemaphore/acquire'
|
|
2
|
-
import { refreshSemaphore } from './multiSemaphore/refresh'
|
|
3
|
-
import { releaseSemaphore } from './multiSemaphore/release'
|
|
4
|
-
import RedisSemaphore from './RedisSemaphore'
|
|
5
|
-
import { LockOptions, RedisClient } from './types'
|
|
6
|
-
|
|
7
|
-
export default class RedisMultiSemaphore extends RedisSemaphore {
|
|
8
|
-
protected _kind = 'multi-semaphore'
|
|
9
|
-
protected _permits: number
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
client: RedisClient,
|
|
13
|
-
key: string,
|
|
14
|
-
limit: number,
|
|
15
|
-
permits: number,
|
|
16
|
-
options?: LockOptions
|
|
17
|
-
) {
|
|
18
|
-
super(client, key, limit, options)
|
|
19
|
-
if (!permits) {
|
|
20
|
-
throw new Error('"permits" is required')
|
|
21
|
-
}
|
|
22
|
-
if (typeof permits !== 'number') {
|
|
23
|
-
throw new Error('"permits" must be a number')
|
|
24
|
-
}
|
|
25
|
-
this._permits = permits
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
protected async _refresh(): Promise<boolean> {
|
|
29
|
-
return await refreshSemaphore(
|
|
30
|
-
this._client,
|
|
31
|
-
this._key,
|
|
32
|
-
this._limit,
|
|
33
|
-
this._permits,
|
|
34
|
-
this._acquireOptions
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
protected async _acquire(): Promise<boolean> {
|
|
39
|
-
return await acquireSemaphore(
|
|
40
|
-
this._client,
|
|
41
|
-
this._key,
|
|
42
|
-
this._limit,
|
|
43
|
-
this._permits,
|
|
44
|
-
this._acquireOptions
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
protected async _release(): Promise<void> {
|
|
49
|
-
await releaseSemaphore(
|
|
50
|
-
this._client,
|
|
51
|
-
this._key,
|
|
52
|
-
this._permits,
|
|
53
|
-
this._identifier
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { RedisClient } from './types'
|
|
2
|
-
|
|
3
|
-
import { Lock } from './Lock'
|
|
4
|
-
import { acquireMutex } from './mutex/acquire'
|
|
5
|
-
import { refreshMutex } from './mutex/refresh'
|
|
6
|
-
import { releaseMutex } from './mutex/release'
|
|
7
|
-
import { LockOptions } from './types'
|
|
8
|
-
|
|
9
|
-
export default class RedisMutex extends Lock {
|
|
10
|
-
protected _kind = 'mutex'
|
|
11
|
-
protected _key: string
|
|
12
|
-
protected _client: RedisClient
|
|
13
|
-
|
|
14
|
-
constructor(client: RedisClient, key: string, options?: LockOptions) {
|
|
15
|
-
super(options)
|
|
16
|
-
if (!client) {
|
|
17
|
-
throw new Error('"client" is required')
|
|
18
|
-
}
|
|
19
|
-
if (!key) {
|
|
20
|
-
throw new Error('"key" is required')
|
|
21
|
-
}
|
|
22
|
-
if (typeof key !== 'string') {
|
|
23
|
-
throw new Error('"key" must be a string')
|
|
24
|
-
}
|
|
25
|
-
this._client = client
|
|
26
|
-
this._key = `mutex:${key}`
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
protected async _refresh(): Promise<boolean> {
|
|
30
|
-
return await refreshMutex(
|
|
31
|
-
this._client,
|
|
32
|
-
this._key,
|
|
33
|
-
this._identifier,
|
|
34
|
-
this._acquireOptions.lockTimeout
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
protected async _acquire(): Promise<boolean> {
|
|
39
|
-
return await acquireMutex(this._client, this._key, this._acquireOptions)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
protected async _release(): Promise<void> {
|
|
43
|
-
await releaseMutex(this._client, this._key, this._identifier)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import RedisMutex from './RedisMutex'
|
|
2
|
-
import { acquireSemaphore } from './semaphore/acquire'
|
|
3
|
-
import { refreshSemaphore } from './semaphore/refresh'
|
|
4
|
-
import { releaseSemaphore } from './semaphore/release'
|
|
5
|
-
import { LockOptions, RedisClient } from './types'
|
|
6
|
-
|
|
7
|
-
export default class RedisSemaphore extends RedisMutex {
|
|
8
|
-
protected _kind = 'semaphore'
|
|
9
|
-
protected _limit: number
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
client: RedisClient,
|
|
13
|
-
key: string,
|
|
14
|
-
limit: number,
|
|
15
|
-
options?: LockOptions
|
|
16
|
-
) {
|
|
17
|
-
super(client, key, options)
|
|
18
|
-
if (!limit) {
|
|
19
|
-
throw new Error('"limit" is required')
|
|
20
|
-
}
|
|
21
|
-
if (typeof limit !== 'number') {
|
|
22
|
-
throw new Error('"limit" must be a number')
|
|
23
|
-
}
|
|
24
|
-
this._key = `semaphore:${key}`
|
|
25
|
-
this._limit = limit
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
protected async _refresh(): Promise<boolean> {
|
|
29
|
-
return await refreshSemaphore(
|
|
30
|
-
this._client,
|
|
31
|
-
this._key,
|
|
32
|
-
this._limit,
|
|
33
|
-
this._acquireOptions
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected async _acquire(): Promise<boolean> {
|
|
38
|
-
return await acquireSemaphore(
|
|
39
|
-
this._client,
|
|
40
|
-
this._key,
|
|
41
|
-
this._limit,
|
|
42
|
-
this._acquireOptions
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
protected async _release(): Promise<void> {
|
|
47
|
-
await releaseSemaphore(this._client, this._key, this._identifier)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { acquireRedlockMultiSemaphore } from './redlockMultiSemaphore/acquire'
|
|
2
|
-
import { refreshRedlockMultiSemaphore } from './redlockMultiSemaphore/refresh'
|
|
3
|
-
import { releaseRedlockMultiSemaphore } from './redlockMultiSemaphore/release'
|
|
4
|
-
import RedlockSemaphore from './RedlockSemaphore'
|
|
5
|
-
import { LockOptions, RedisClient } from './types'
|
|
6
|
-
|
|
7
|
-
export default class RedlockMultiSemaphore extends RedlockSemaphore {
|
|
8
|
-
protected _kind = 'redlock-multi-semaphore'
|
|
9
|
-
protected _permits: number
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
clients: RedisClient[],
|
|
13
|
-
key: string,
|
|
14
|
-
limit: number,
|
|
15
|
-
permits: number,
|
|
16
|
-
options?: LockOptions
|
|
17
|
-
) {
|
|
18
|
-
super(clients, key, limit, options)
|
|
19
|
-
if (!permits) {
|
|
20
|
-
throw new Error('"permits" is required')
|
|
21
|
-
}
|
|
22
|
-
if (typeof permits !== 'number') {
|
|
23
|
-
throw new Error('"permits" must be a number')
|
|
24
|
-
}
|
|
25
|
-
this._permits = permits
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
protected async _refresh(): Promise<boolean> {
|
|
29
|
-
return await refreshRedlockMultiSemaphore(
|
|
30
|
-
this._clients,
|
|
31
|
-
this._key,
|
|
32
|
-
this._limit,
|
|
33
|
-
this._permits,
|
|
34
|
-
this._acquireOptions
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
protected async _acquire(): Promise<boolean> {
|
|
39
|
-
return await acquireRedlockMultiSemaphore(
|
|
40
|
-
this._clients,
|
|
41
|
-
this._key,
|
|
42
|
-
this._limit,
|
|
43
|
-
this._permits,
|
|
44
|
-
this._acquireOptions
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
protected async _release(): Promise<void> {
|
|
49
|
-
await releaseRedlockMultiSemaphore(
|
|
50
|
-
this._clients,
|
|
51
|
-
this._key,
|
|
52
|
-
this._permits,
|
|
53
|
-
this._identifier
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Lock } from './Lock'
|
|
2
|
-
import { defaultTimeoutOptions } from './misc'
|
|
3
|
-
import { acquireRedlockMutex } from './redlockMutex/acquire'
|
|
4
|
-
import { refreshRedlockMutex } from './redlockMutex/refresh'
|
|
5
|
-
import { releaseRedlockMutex } from './redlockMutex/release'
|
|
6
|
-
import { LockOptions, RedisClient } from './types'
|
|
7
|
-
|
|
8
|
-
export default class RedlockMutex extends Lock {
|
|
9
|
-
protected _kind = 'redlock-mutex'
|
|
10
|
-
protected _key: string
|
|
11
|
-
protected _clients: RedisClient[]
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
clients: RedisClient[],
|
|
15
|
-
key: string,
|
|
16
|
-
options: LockOptions = defaultTimeoutOptions
|
|
17
|
-
) {
|
|
18
|
-
super(options)
|
|
19
|
-
if (!clients || !Array.isArray(clients)) {
|
|
20
|
-
throw new Error('"clients" array is required')
|
|
21
|
-
}
|
|
22
|
-
if (!key) {
|
|
23
|
-
throw new Error('"key" is required')
|
|
24
|
-
}
|
|
25
|
-
if (typeof key !== 'string') {
|
|
26
|
-
throw new Error('"key" must be a string')
|
|
27
|
-
}
|
|
28
|
-
this._clients = clients
|
|
29
|
-
this._key = `mutex:${key}`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
protected async _refresh(): Promise<boolean> {
|
|
33
|
-
return await refreshRedlockMutex(
|
|
34
|
-
this._clients,
|
|
35
|
-
this._key,
|
|
36
|
-
this._identifier,
|
|
37
|
-
this._acquireOptions.lockTimeout
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
protected async _acquire(): Promise<boolean> {
|
|
42
|
-
return await acquireRedlockMutex(
|
|
43
|
-
this._clients,
|
|
44
|
-
this._key,
|
|
45
|
-
this._acquireOptions
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
protected async _release(): Promise<void> {
|
|
50
|
-
await releaseRedlockMutex(this._clients, this._key, this._identifier)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import RedlockMutex from './RedlockMutex'
|
|
2
|
-
import { acquireRedlockSemaphore } from './redlockSemaphore/acquire'
|
|
3
|
-
import { refreshRedlockSemaphore } from './redlockSemaphore/refresh'
|
|
4
|
-
import { releaseRedlockSemaphore } from './redlockSemaphore/release'
|
|
5
|
-
import { LockOptions, RedisClient } from './types'
|
|
6
|
-
|
|
7
|
-
export default class RedlockSemaphore extends RedlockMutex {
|
|
8
|
-
protected _kind = 'redlock-semaphore'
|
|
9
|
-
protected _limit: number
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
clients: RedisClient[],
|
|
13
|
-
key: string,
|
|
14
|
-
limit: number,
|
|
15
|
-
options?: LockOptions
|
|
16
|
-
) {
|
|
17
|
-
super(clients, key, options)
|
|
18
|
-
if (!limit) {
|
|
19
|
-
throw new Error('"limit" is required')
|
|
20
|
-
}
|
|
21
|
-
if (typeof limit !== 'number') {
|
|
22
|
-
throw new Error('"limit" must be a number')
|
|
23
|
-
}
|
|
24
|
-
this._key = `semaphore:${key}`
|
|
25
|
-
this._limit = limit
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
protected async _refresh(): Promise<boolean> {
|
|
29
|
-
return await refreshRedlockSemaphore(
|
|
30
|
-
this._clients,
|
|
31
|
-
this._key,
|
|
32
|
-
this._limit,
|
|
33
|
-
this._acquireOptions
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected async _acquire(): Promise<boolean> {
|
|
38
|
-
return await acquireRedlockSemaphore(
|
|
39
|
-
this._clients,
|
|
40
|
-
this._key,
|
|
41
|
-
this._limit,
|
|
42
|
-
this._acquireOptions
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
protected async _release(): Promise<void> {
|
|
47
|
-
await releaseRedlockSemaphore(this._clients, this._key, this._identifier)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default class LostLockError extends Error {}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default class TimeoutError extends Error {}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import MultiSemaphore from './RedisMultiSemaphore'
|
|
2
|
-
import Mutex from './RedisMutex'
|
|
3
|
-
import Semaphore from './RedisSemaphore'
|
|
4
|
-
import RedlockMultiSemaphore from './RedlockMultiSemaphore'
|
|
5
|
-
import RedlockMutex from './RedlockMutex'
|
|
6
|
-
import RedlockSemaphore from './RedlockSemaphore'
|
|
7
|
-
import LostLockError from './errors/LostLockError'
|
|
8
|
-
import TimeoutError from './errors/TimeoutError'
|
|
9
|
-
|
|
10
|
-
export { defaultTimeoutOptions } from './misc'
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
Mutex,
|
|
14
|
-
Semaphore,
|
|
15
|
-
MultiSemaphore,
|
|
16
|
-
RedlockMutex,
|
|
17
|
-
RedlockSemaphore,
|
|
18
|
-
RedlockMultiSemaphore,
|
|
19
|
-
LostLockError,
|
|
20
|
-
TimeoutError
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type { LockLostCallback, TimeoutOptions, LockOptions } from './types'
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import LostLockError from './errors/LostLockError'
|
|
2
|
-
|
|
3
|
-
export const defaultTimeoutOptions = {
|
|
4
|
-
lockTimeout: 10000,
|
|
5
|
-
acquireTimeout: 10000,
|
|
6
|
-
acquireAttemptsLimit: Number.POSITIVE_INFINITY,
|
|
7
|
-
retryInterval: 10
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function defaultOnLockLost(err: LostLockError): never {
|
|
11
|
-
throw err
|
|
12
|
-
}
|
|
@@ -1,53 +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:multi-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
|
-
permits: 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
|
-
let now
|
|
33
|
-
while ((now = Date.now()) < end && ++attempt <= acquireAttemptsLimit) {
|
|
34
|
-
debug(key, identifier, limit, lockTimeout, 'attempt', attempt)
|
|
35
|
-
const result = await acquireLua(client, [
|
|
36
|
-
key,
|
|
37
|
-
limit,
|
|
38
|
-
permits,
|
|
39
|
-
identifier,
|
|
40
|
-
lockTimeout,
|
|
41
|
-
now
|
|
42
|
-
])
|
|
43
|
-
debug(key, 'result', typeof result, result)
|
|
44
|
-
if (result === 1) {
|
|
45
|
-
debug(key, identifier, 'acquired')
|
|
46
|
-
return true
|
|
47
|
-
} else {
|
|
48
|
-
await delay(retryInterval)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
debug(key, identifier, limit, lockTimeout, 'timeout or reach limit')
|
|
52
|
-
return false
|
|
53
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { createEval } from '../../utils/index'
|
|
2
|
-
|
|
3
|
-
export const acquireLua = createEval<
|
|
4
|
-
[string, number, number, string, number, number],
|
|
5
|
-
0 | 1
|
|
6
|
-
>(
|
|
7
|
-
`
|
|
8
|
-
local key = KEYS[1]
|
|
9
|
-
local limit = tonumber(ARGV[1])
|
|
10
|
-
local permits = tonumber(ARGV[2])
|
|
11
|
-
local identifier = ARGV[3]
|
|
12
|
-
local lockTimeout = tonumber(ARGV[4])
|
|
13
|
-
local now = tonumber(ARGV[5])
|
|
14
|
-
local expiredTimestamp = now - lockTimeout
|
|
15
|
-
local args = {}
|
|
16
|
-
|
|
17
|
-
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
18
|
-
|
|
19
|
-
if (redis.call('zcard', key) + permits) <= limit then
|
|
20
|
-
for i=0, permits - 1 do
|
|
21
|
-
table.insert(args, now)
|
|
22
|
-
table.insert(args, identifier .. '_' .. i)
|
|
23
|
-
end
|
|
24
|
-
redis.call('zadd', key, unpack(args))
|
|
25
|
-
redis.call('pexpire', key, lockTimeout)
|
|
26
|
-
return 1
|
|
27
|
-
else
|
|
28
|
-
return 0
|
|
29
|
-
end`,
|
|
30
|
-
1
|
|
31
|
-
)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import createDebug from 'debug'
|
|
2
|
-
import { RedisClient } from '../../types'
|
|
3
|
-
import { refreshLua } from './lua'
|
|
4
|
-
|
|
5
|
-
const debug = createDebug('redis-semaphore:multi-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
|
-
permits: number,
|
|
17
|
-
options: Options
|
|
18
|
-
): Promise<boolean> {
|
|
19
|
-
const { identifier, lockTimeout } = options
|
|
20
|
-
const now = Date.now()
|
|
21
|
-
debug(key, identifier, now)
|
|
22
|
-
const result = await refreshLua(client, [
|
|
23
|
-
key,
|
|
24
|
-
limit,
|
|
25
|
-
permits,
|
|
26
|
-
identifier,
|
|
27
|
-
lockTimeout,
|
|
28
|
-
now
|
|
29
|
-
])
|
|
30
|
-
debug('result', typeof result, result)
|
|
31
|
-
return result === 1
|
|
32
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { createEval } from '../../utils/index'
|
|
2
|
-
|
|
3
|
-
export const refreshLua = createEval<
|
|
4
|
-
[string, number, number, string, number, number],
|
|
5
|
-
0 | 1
|
|
6
|
-
>(
|
|
7
|
-
`
|
|
8
|
-
local key = KEYS[1]
|
|
9
|
-
local limit = tonumber(ARGV[1])
|
|
10
|
-
local permits = tonumber(ARGV[2])
|
|
11
|
-
local identifier = ARGV[3]
|
|
12
|
-
local lockTimeout = tonumber(ARGV[4])
|
|
13
|
-
local now = tonumber(ARGV[5])
|
|
14
|
-
local expiredTimestamp = now - lockTimeout
|
|
15
|
-
local args = {}
|
|
16
|
-
|
|
17
|
-
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
18
|
-
|
|
19
|
-
if redis.call('zscore', key, identifier .. '_0') then
|
|
20
|
-
for i=0, permits - 1 do
|
|
21
|
-
table.insert(args, now)
|
|
22
|
-
table.insert(args, identifier .. '_' .. i)
|
|
23
|
-
end
|
|
24
|
-
redis.call('zadd', key, unpack(args))
|
|
25
|
-
redis.call('pexpire', key, lockTimeout)
|
|
26
|
-
return 1
|
|
27
|
-
else
|
|
28
|
-
return 0
|
|
29
|
-
end`,
|
|
30
|
-
1
|
|
31
|
-
)
|