effect-distributed-lock 0.0.5 → 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.
Files changed (97) hide show
  1. package/README.md +44 -6
  2. package/examples/concurrent.ts +111 -0
  3. package/examples/{index.ts → kitchen-sink.ts} +3 -1
  4. package/package.json +1 -1
  5. package/src/Backing.ts +21 -17
  6. package/src/DistributedSemaphore.ts +71 -16
  7. package/src/RedisBacking.ts +97 -7
  8. package/redis-semaphore/.codeclimate.yml +0 -5
  9. package/redis-semaphore/.fossa.yml +0 -14
  10. package/redis-semaphore/.github/dependabot.yml +0 -6
  11. package/redis-semaphore/.github/workflows/branches.yml +0 -39
  12. package/redis-semaphore/.github/workflows/pull-requests.yml +0 -35
  13. package/redis-semaphore/.mocharc.yaml +0 -6
  14. package/redis-semaphore/.prettierrc +0 -6
  15. package/redis-semaphore/.snyk +0 -4
  16. package/redis-semaphore/.yarnrc.yml +0 -2
  17. package/redis-semaphore/CHANGELOG.md +0 -70
  18. package/redis-semaphore/Dockerfile +0 -5
  19. package/redis-semaphore/LICENSE +0 -21
  20. package/redis-semaphore/README.md +0 -445
  21. package/redis-semaphore/docker-compose.yml +0 -31
  22. package/redis-semaphore/eslint.config.mjs +0 -73
  23. package/redis-semaphore/package.json +0 -79
  24. package/redis-semaphore/setup-redis-servers.sh +0 -2
  25. package/redis-semaphore/src/Lock.ts +0 -172
  26. package/redis-semaphore/src/RedisMultiSemaphore.ts +0 -56
  27. package/redis-semaphore/src/RedisMutex.ts +0 -45
  28. package/redis-semaphore/src/RedisSemaphore.ts +0 -49
  29. package/redis-semaphore/src/RedlockMultiSemaphore.ts +0 -56
  30. package/redis-semaphore/src/RedlockMutex.ts +0 -52
  31. package/redis-semaphore/src/RedlockSemaphore.ts +0 -49
  32. package/redis-semaphore/src/errors/LostLockError.ts +0 -1
  33. package/redis-semaphore/src/errors/TimeoutError.ts +0 -1
  34. package/redis-semaphore/src/index.ts +0 -23
  35. package/redis-semaphore/src/misc.ts +0 -12
  36. package/redis-semaphore/src/multiSemaphore/acquire/index.ts +0 -53
  37. package/redis-semaphore/src/multiSemaphore/acquire/lua.ts +0 -31
  38. package/redis-semaphore/src/multiSemaphore/refresh/index.ts +0 -32
  39. package/redis-semaphore/src/multiSemaphore/refresh/lua.ts +0 -31
  40. package/redis-semaphore/src/multiSemaphore/release/index.ts +0 -22
  41. package/redis-semaphore/src/multiSemaphore/release/lua.ts +0 -17
  42. package/redis-semaphore/src/mutex/acquire.ts +0 -42
  43. package/redis-semaphore/src/mutex/refresh.ts +0 -37
  44. package/redis-semaphore/src/mutex/release.ts +0 -30
  45. package/redis-semaphore/src/redlockMultiSemaphore/acquire.ts +0 -56
  46. package/redis-semaphore/src/redlockMultiSemaphore/refresh.ts +0 -68
  47. package/redis-semaphore/src/redlockMultiSemaphore/release.ts +0 -19
  48. package/redis-semaphore/src/redlockMutex/acquire.ts +0 -54
  49. package/redis-semaphore/src/redlockMutex/refresh.ts +0 -53
  50. package/redis-semaphore/src/redlockMutex/release.ts +0 -19
  51. package/redis-semaphore/src/redlockSemaphore/acquire.ts +0 -55
  52. package/redis-semaphore/src/redlockSemaphore/refresh.ts +0 -60
  53. package/redis-semaphore/src/redlockSemaphore/release.ts +0 -18
  54. package/redis-semaphore/src/semaphore/acquire/index.ts +0 -52
  55. package/redis-semaphore/src/semaphore/acquire/lua.ts +0 -25
  56. package/redis-semaphore/src/semaphore/refresh/index.ts +0 -31
  57. package/redis-semaphore/src/semaphore/refresh/lua.ts +0 -25
  58. package/redis-semaphore/src/semaphore/release.ts +0 -14
  59. package/redis-semaphore/src/types.ts +0 -63
  60. package/redis-semaphore/src/utils/createEval.ts +0 -45
  61. package/redis-semaphore/src/utils/index.ts +0 -13
  62. package/redis-semaphore/src/utils/redlock.ts +0 -7
  63. package/redis-semaphore/test/init.test.ts +0 -9
  64. package/redis-semaphore/test/redisClient.ts +0 -82
  65. package/redis-semaphore/test/setup.ts +0 -6
  66. package/redis-semaphore/test/shell.test.ts +0 -15
  67. package/redis-semaphore/test/shell.ts +0 -48
  68. package/redis-semaphore/test/src/Lock.test.ts +0 -37
  69. package/redis-semaphore/test/src/RedisMultiSemaphore.test.ts +0 -425
  70. package/redis-semaphore/test/src/RedisMutex.test.ts +0 -334
  71. package/redis-semaphore/test/src/RedisSemaphore.test.ts +0 -367
  72. package/redis-semaphore/test/src/RedlockMultiSemaphore.test.ts +0 -671
  73. package/redis-semaphore/test/src/RedlockMutex.test.ts +0 -328
  74. package/redis-semaphore/test/src/RedlockSemaphore.test.ts +0 -579
  75. package/redis-semaphore/test/src/index.test.ts +0 -22
  76. package/redis-semaphore/test/src/multiSemaphore/acquire/index.test.ts +0 -51
  77. package/redis-semaphore/test/src/multiSemaphore/acquire/internal.test.ts +0 -67
  78. package/redis-semaphore/test/src/multiSemaphore/refresh/index.test.ts +0 -52
  79. package/redis-semaphore/test/src/multiSemaphore/release/index.test.ts +0 -18
  80. package/redis-semaphore/test/src/mutex/acquire.test.ts +0 -78
  81. package/redis-semaphore/test/src/mutex/refresh.test.ts +0 -22
  82. package/redis-semaphore/test/src/mutex/release.test.ts +0 -17
  83. package/redis-semaphore/test/src/redlockMutex/acquire.test.ts +0 -90
  84. package/redis-semaphore/test/src/redlockMutex/refresh.test.ts +0 -27
  85. package/redis-semaphore/test/src/redlockMutex/release.test.ts +0 -17
  86. package/redis-semaphore/test/src/semaphore/acquire/index.test.ts +0 -49
  87. package/redis-semaphore/test/src/semaphore/acquire/internal.test.ts +0 -65
  88. package/redis-semaphore/test/src/semaphore/refresh/index.test.ts +0 -44
  89. package/redis-semaphore/test/src/semaphore/release.test.ts +0 -18
  90. package/redis-semaphore/test/src/utils/eval.test.ts +0 -22
  91. package/redis-semaphore/test/src/utils/index.test.ts +0 -19
  92. package/redis-semaphore/test/src/utils/redlock.test.ts +0 -31
  93. package/redis-semaphore/test/unhandledRejection.ts +0 -28
  94. package/redis-semaphore/tsconfig.build-commonjs.json +0 -9
  95. package/redis-semaphore/tsconfig.build-es.json +0 -9
  96. package/redis-semaphore/tsconfig.json +0 -11
  97. package/redis-semaphore/yarn.lock +0 -5338
@@ -1,79 +0,0 @@
1
- {
2
- "name": "redis-semaphore",
3
- "version": "5.6.2",
4
- "description": "Distributed mutex and semaphore based on Redis",
5
- "main": "lib/index.js",
6
- "scripts": {
7
- "lint": "eslint --ext .js,.ts .",
8
- "test": "mocha",
9
- "test-ci-with-coverage": "nyc mocha && nyc report --reporter=text-lcov | coveralls",
10
- "coverage-html": "nyc mocha && nyc report --reporter=html",
11
- "converalls": "nyc mocha && nyc report --reporter=text-lcov | coveralls",
12
- "dev": "mocha -w",
13
- "build": "yarn build-commonjs",
14
- "build-commonjs": "rm -rf lib && yarn tsc -b tsconfig.build-commonjs.json",
15
- "build-es": "rm -rf es && yarn tsc -b tsconfig.build-es.json",
16
- "preversion": "yarn lint && yarn test && yarn build"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git@github.com:swarthy/redis-semaphore.git"
21
- },
22
- "keywords": [
23
- "redis",
24
- "redlock",
25
- "mutex",
26
- "semaphore"
27
- ],
28
- "author": "Alexander Mochalin (horroshow@mail.ru)",
29
- "license": "MIT",
30
- "devDependencies": {
31
- "@swarthy/wait-for": "^2.1.1",
32
- "@swc-node/register": "1.10.10",
33
- "@swc/core": "1.11.11",
34
- "@types/chai": "^4.3.20",
35
- "@types/chai-as-promised": "^7.1.8",
36
- "@types/debug": "^4.1.12",
37
- "@types/ioredis-mock": "^8.2.5",
38
- "@types/mocha": "^10.0.10",
39
- "@types/node": "22.13.11",
40
- "@types/sinon": "^17.0.4",
41
- "@types/sinon-chai": "^3.2.12",
42
- "@typescript-eslint/eslint-plugin": "8.27.0",
43
- "@typescript-eslint/parser": "8.27.0",
44
- "benchmark": "^2.1.4",
45
- "chai": "4.5.0",
46
- "chai-as-promised": "7.1.2",
47
- "coveralls": "^3.1.1",
48
- "eslint": "9.23.0",
49
- "eslint-plugin-node": "11.1.0",
50
- "ioredis": "5.6.0",
51
- "ioredis-mock": "8.9.0",
52
- "mocha": "11.1.0",
53
- "mocha-lcov-reporter": "^1.3.0",
54
- "nyc": "^17.1.0",
55
- "sinon": "19.0.4",
56
- "sinon-chai": "3.7.0",
57
- "snyk": "1.1296.0",
58
- "ts-node": "^10.9.2",
59
- "typescript": "^5.8.2"
60
- },
61
- "engines": {
62
- "node": ">= 14.17.0"
63
- },
64
- "peerDependencies": {
65
- "ioredis": "^4.1.0 || ^5"
66
- },
67
- "peerDependenciesMeta": {
68
- "ioredis": {
69
- "optional": true
70
- }
71
- },
72
- "dependencies": {
73
- "debug": "^4.4.0"
74
- },
75
- "packageManager": "yarn@4.1.0+sha256.81a00df816059803e6b5148acf03ce313cad36b7f6e5af6efa040a15981a6ffb",
76
- "files": [
77
- "lib/"
78
- ]
79
- }
@@ -1,2 +0,0 @@
1
- #!/bin/sh
2
- docker compose up -d redis1 redis2 redis3
@@ -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
- )