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.
- package/README.md +44 -6
- package/examples/concurrent.ts +111 -0
- package/examples/{index.ts → kitchen-sink.ts} +3 -1
- package/package.json +1 -1
- package/src/Backing.ts +21 -17
- package/src/DistributedSemaphore.ts +71 -16
- package/src/RedisBacking.ts +97 -7
- 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/redis-semaphore/.snyk
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
### redis-semaphore@5.6.2
|
|
2
|
-
- Fixed implicit import from `src`
|
|
3
|
-
- Removed `src` folder from NPM package
|
|
4
|
-
|
|
5
|
-
### redis-semaphore@5.6.1
|
|
6
|
-
- Removed `module` field from `package.json`
|
|
7
|
-
|
|
8
|
-
### redis-semaphore@5.6.0
|
|
9
|
-
- Added interface compatible client support (ex. `ioredis-mock`)
|
|
10
|
-
- Removed `instanceof Redis` validation in constructor
|
|
11
|
-
- `ioredis` marked as optional peerDependency, explicit `ioredis` install is required now
|
|
12
|
-
|
|
13
|
-
### redis-semaphore@5.5.1
|
|
14
|
-
- Fix race condition for refresh started before release and finished after release
|
|
15
|
-
|
|
16
|
-
### redis-semaphore@5.5.0
|
|
17
|
-
|
|
18
|
-
- Added `identifier` constructor option.
|
|
19
|
-
- Added `acquiredExternally` constructor option.
|
|
20
|
-
- Option `externallyAcquiredIdentifier` **DEPRECATED**.
|
|
21
|
-
- Option `identifierSuffix` **DEPRECATED**.
|
|
22
|
-
|
|
23
|
-
### redis-semaphore@5.4.0
|
|
24
|
-
|
|
25
|
-
- Added `identifierSuffix` option, usefull for tracing app instance which locked resource
|
|
26
|
-
|
|
27
|
-
### redis-semaphore@5.3.1
|
|
28
|
-
|
|
29
|
-
- Fixed reacquire expired resource in refresh
|
|
30
|
-
|
|
31
|
-
### redis-semaphore@5.3.0
|
|
32
|
-
|
|
33
|
-
- Added `stopRefresh` method
|
|
34
|
-
- Added `externallyAcquiredIdentifier` optional constructor option
|
|
35
|
-
- Removed `uuid` dependency
|
|
36
|
-
|
|
37
|
-
### redis-semaphore@5.2.0
|
|
38
|
-
|
|
39
|
-
- Added `acquireAttemptsLimit` method
|
|
40
|
-
|
|
41
|
-
### redis-semaphore@5.1.0
|
|
42
|
-
|
|
43
|
-
- Added `tryAcquire`
|
|
44
|
-
|
|
45
|
-
### redis-semaphore@5.0.0
|
|
46
|
-
|
|
47
|
-
- **Breadking change:** Drop Node.js v10.x, v12.x support
|
|
48
|
-
- Added `ioredis@5` support
|
|
49
|
-
|
|
50
|
-
### redis-semaphore@4.1.0
|
|
51
|
-
|
|
52
|
-
- Added `.isAcquired` property on all locks
|
|
53
|
-
- Added `onLostLock` constructor option. By default throws unhandled error.
|
|
54
|
-
|
|
55
|
-
### redis-semaphore@4.0.0
|
|
56
|
-
|
|
57
|
-
- **Breaking change:** `Mutex`, `Semaphore`, `MultiSemaphore` not longer support `Cluster`. For multi-node case use `Redlock*` instead.
|
|
58
|
-
- Added `RedlockMutex`, `RedlockSemaphore`, `RedlockMultiSemaphore`
|
|
59
|
-
- Internals refactored
|
|
60
|
-
|
|
61
|
-
### redis-semaphore@3.2.0
|
|
62
|
-
|
|
63
|
-
- Added `MultiSemaphore`
|
|
64
|
-
|
|
65
|
-
### redis-semaphore@3.0.0
|
|
66
|
-
|
|
67
|
-
- **Breaking change:** `FairSemaphore` has been removed. Use `Semaphore` instead (has the same "fairness")
|
|
68
|
-
- the `acquire` method in `Semaphore` no longer returns a boolean. Instead, it throws an error if it cannot acquire, and if it doesn't throw, you can assume it worked.
|
|
69
|
-
- Internal code has been cleaned up
|
|
70
|
-
- Added more test, include synthetic node unsynchroned clocks
|
package/redis-semaphore/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2018 Alexander Mochalin
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
# redis-semaphore
|
|
2
|
-
|
|
3
|
-
[![NPM version][npm-image]][npm-url]
|
|
4
|
-
[![Build status][ci-image]][ci-url]
|
|
5
|
-
![FOSSA Status][typescript-image]
|
|
6
|
-
[![Coverage Status][coverage-image]][coverage-url]
|
|
7
|
-
[![Maintainability][codeclimate-image]][codeclimate-url]
|
|
8
|
-
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
9
|
-
[![FOSSA Status][fossa-badge-image]][fossa-badge-url]
|
|
10
|
-
|
|
11
|
-
[Mutex](<https://en.wikipedia.org/wiki/Lock_(computer_science)>) and [Semaphore](<https://en.wikipedia.org/wiki/Semaphore_(programming)>) implementations based on [Redis](https://redis.io/) ready for distributed systems
|
|
12
|
-
|
|
13
|
-
## Features
|
|
14
|
-
|
|
15
|
-
- Fail-safe (all actions performed by LUA scripts (atomic))
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
### Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install --save redis-semaphore ioredis
|
|
23
|
-
# or
|
|
24
|
-
yarn add redis-semaphore ioredis
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
ioredis is the officially supported Redis client. This library's test code runs on it.
|
|
28
|
-
|
|
29
|
-
Users of other Redis clients should ensure ioredis-compatible API (see src/types.ts) when creating lock objects.
|
|
30
|
-
|
|
31
|
-
### Mutex
|
|
32
|
-
|
|
33
|
-
> See [RedisLabs: Locks with timeouts](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-2-distributed-locking/6-2-5-locks-with-timeouts/)
|
|
34
|
-
|
|
35
|
-
##### new Mutex(redisClient, key [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8, identifier = crypto.randomUUID() }])
|
|
36
|
-
|
|
37
|
-
- `redisClient` - **required**, configured `redis` client
|
|
38
|
-
- `key` - **required**, key for locking resource (final key in redis: `mutex:<key>`)
|
|
39
|
-
- `options` - _optional_
|
|
40
|
-
- `lockTimeout` - _optional_ ms, time after mutex will be auto released (expired)
|
|
41
|
-
- `acquireTimeout` - _optional_ ms, max timeout for `.acquire()` call
|
|
42
|
-
- `acquireAttemptsLimit` - _optional_ max number of attempts to be made in `.acquire()` call
|
|
43
|
-
- `retryInterval` - _optional_ ms, time between acquire attempts if resource locked
|
|
44
|
-
- `refreshInterval` - _optional_ ms, auto-refresh interval; to disable auto-refresh behaviour set `0`
|
|
45
|
-
- `identifier` - _optional_ uuid, custom mutex identifier. Must be unique between parallel executors, otherwise multiple locks with same identifier *can* be treated as the same lock holder. Override only if you know what you are doing (see `acquiredExternally` option).
|
|
46
|
-
- `acquiredExternally` - _optional_ `true`, If `identifier` provided and `acquiredExternally` is `true` then `_refresh` will be used instead of `_acquire` in `.tryAcquire()`/`.acquire()`. Useful for lock sharing between processes: acquire in scheduler, refresh and release in handler.
|
|
47
|
-
- `onLockLost` - _optional_ function, called when lock loss is detected due refresh cycle; default onLockLost throws unhandled LostLockError
|
|
48
|
-
|
|
49
|
-
#### Example
|
|
50
|
-
|
|
51
|
-
```javascript
|
|
52
|
-
const Mutex = require('redis-semaphore').Mutex
|
|
53
|
-
const Redis = require('ioredis')
|
|
54
|
-
|
|
55
|
-
// TypeScript
|
|
56
|
-
// import { Mutex } from 'redis-semaphore'
|
|
57
|
-
// import Redis from 'ioredis'
|
|
58
|
-
|
|
59
|
-
const redisClient = new Redis()
|
|
60
|
-
|
|
61
|
-
async function doSomething() {
|
|
62
|
-
const mutex = new Mutex(redisClient, 'lockingResource')
|
|
63
|
-
await mutex.acquire()
|
|
64
|
-
try {
|
|
65
|
-
// critical code
|
|
66
|
-
} finally {
|
|
67
|
-
await mutex.release()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
#### Example with lost lock handling
|
|
73
|
-
|
|
74
|
-
```javascript
|
|
75
|
-
async function doSomething() {
|
|
76
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
77
|
-
// By default onLockLost throws unhandled LostLockError
|
|
78
|
-
onLockLost(err) {
|
|
79
|
-
console.error(err)
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
await mutex.acquire()
|
|
83
|
-
try {
|
|
84
|
-
while (mutex.isAcquired) {
|
|
85
|
-
// critical cycle iteration
|
|
86
|
-
}
|
|
87
|
-
} finally {
|
|
88
|
-
// It's safe to always call release, because if lock is no longer belongs to this mutex, .release() will have no effect
|
|
89
|
-
await mutex.release()
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
#### Example with optional lock
|
|
95
|
-
|
|
96
|
-
```javascript
|
|
97
|
-
async function doSomething() {
|
|
98
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
99
|
-
acquireAttemptsLimit: 1
|
|
100
|
-
})
|
|
101
|
-
const lockAcquired = await mutex.tryAcquire()
|
|
102
|
-
if (!lockAcquired) {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
while (mutex.isAcquired) {
|
|
107
|
-
// critical cycle iteration
|
|
108
|
-
}
|
|
109
|
-
} finally {
|
|
110
|
-
// It's safe to always call release, because if lock is no longer belongs to this mutex, .release() will have no effect
|
|
111
|
-
await mutex.release()
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### Example with temporary refresh
|
|
117
|
-
|
|
118
|
-
```javascript
|
|
119
|
-
async function doSomething() {
|
|
120
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
121
|
-
lockTimeout: 120000,
|
|
122
|
-
refreshInterval: 15000
|
|
123
|
-
})
|
|
124
|
-
const lockAcquired = await mutex.tryAcquire()
|
|
125
|
-
if (!lockAcquired) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
// critical cycle iteration
|
|
130
|
-
} finally {
|
|
131
|
-
// We want to let lock expire over time after operation is finished
|
|
132
|
-
await mutex.stopRefresh()
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
#### Example with dynamically adjusting existing lock
|
|
138
|
-
|
|
139
|
-
```javascript
|
|
140
|
-
const Mutex = require('redis-semaphore').Mutex
|
|
141
|
-
const Redis = require('ioredis')
|
|
142
|
-
|
|
143
|
-
// TypeScript
|
|
144
|
-
// import { Mutex } from 'redis-semaphore'
|
|
145
|
-
// import Redis from 'ioredis'
|
|
146
|
-
|
|
147
|
-
const redisClient = new Redis()
|
|
148
|
-
|
|
149
|
-
// This creates an original lock
|
|
150
|
-
const preMutex = new Mutex(redisClient, 'lockingResource', {
|
|
151
|
-
lockTimeout: 10 * 1e3, // lock for 10s
|
|
152
|
-
refreshInterval: 0
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// This modifies lock with a new TTL and starts refresh
|
|
156
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
157
|
-
identifier: preMutex.identifier,
|
|
158
|
-
acquiredExternally: true, // required in this case
|
|
159
|
-
lockTimeout: 30 * 60 * 1e3, // lock for 30min
|
|
160
|
-
refreshInterval: 60 * 1e3
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
#### Example with shared lock between scheduler and handler apps
|
|
166
|
-
|
|
167
|
-
```javascript
|
|
168
|
-
const Mutex = require('redis-semaphore').Mutex
|
|
169
|
-
const Redis = require('ioredis')
|
|
170
|
-
|
|
171
|
-
// TypeScript
|
|
172
|
-
// import { Mutex } from 'redis-semaphore'
|
|
173
|
-
// import Redis from 'ioredis'
|
|
174
|
-
|
|
175
|
-
const redisClient = new Redis()
|
|
176
|
-
|
|
177
|
-
// scheduler app code
|
|
178
|
-
async function every10MinutesCronScheduler() {
|
|
179
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
180
|
-
lockTimeout: 30 * 60 * 1e3, // lock for 30min
|
|
181
|
-
refreshInterval: 0
|
|
182
|
-
})
|
|
183
|
-
if (await mutex.tryAcquire()) {
|
|
184
|
-
someQueue.publish({ mutexIdentifier: mutex.identifier })
|
|
185
|
-
} else {
|
|
186
|
-
logger.info('Job already scheduled. Do nothing in current cron cycle')
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// handler app code
|
|
191
|
-
async function queueHandler(queueMessageData) {
|
|
192
|
-
const { mutexIdentifier } = queueMessageData
|
|
193
|
-
const mutex = new Mutex(redisClient, 'lockingResource', {
|
|
194
|
-
lockTimeout: 10 * 1e3, // 10sec
|
|
195
|
-
identifier: mutexIdentifier,
|
|
196
|
-
acquiredExternally: true // required in this case
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// actually will do `refresh` with new lockTimeout instead of acquire
|
|
200
|
-
// if mutex was locked by another process or lock was expired - exception will be thrown (default refresh behavior)
|
|
201
|
-
await mutex.acquire()
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
// critical code
|
|
205
|
-
} finally {
|
|
206
|
-
await mutex.release()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Semaphore
|
|
212
|
-
|
|
213
|
-
> See [RedisLabs: Basic counting sempahore](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/)
|
|
214
|
-
|
|
215
|
-
This implementation is slightly different from the algorithm described in the book, but the main idea has not changed.
|
|
216
|
-
|
|
217
|
-
`zrank` check replaced with `zcard`, so now it is fair as [RedisLabs: Fair semaphore](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/6-3-2-fair-semaphores/) (see tests).
|
|
218
|
-
|
|
219
|
-
In edge cases (node time difference is greater than `lockTimeout`) both algorithms are not fair due cleanup stage (removing expired members from sorted set), so `FairSemaphore` API has been removed (it's safe to replace it with `Semaphore`).
|
|
220
|
-
|
|
221
|
-
Most reliable way to use: `lockTimeout` is greater than possible node clock differences, `refreshInterval` is not 0 and is less enough than `lockTimeout` (by default is `lockTimeout * 0.8`)
|
|
222
|
-
|
|
223
|
-
##### new Semaphore(redisClient, key, maxCount [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])
|
|
224
|
-
|
|
225
|
-
- `redisClient` - **required**, configured `redis` client
|
|
226
|
-
- `key` - **required**, key for locking resource (final key in redis: `semaphore:<key>`)
|
|
227
|
-
- `maxCount` - **required**, maximum simultaneously resource usage count
|
|
228
|
-
- `options` _optional_ See `Mutex` options
|
|
229
|
-
|
|
230
|
-
#### Example
|
|
231
|
-
|
|
232
|
-
```javascript
|
|
233
|
-
const Semaphore = require('redis-semaphore').Semaphore
|
|
234
|
-
const Redis = require('ioredis')
|
|
235
|
-
|
|
236
|
-
// TypeScript
|
|
237
|
-
// import { Semaphore } from 'redis-semaphore'
|
|
238
|
-
// import Redis from 'ioredis'
|
|
239
|
-
|
|
240
|
-
const redisClient = new Redis()
|
|
241
|
-
|
|
242
|
-
async function doSomething() {
|
|
243
|
-
const semaphore = new Semaphore(redisClient, 'lockingResource', 5)
|
|
244
|
-
await semaphore.acquire()
|
|
245
|
-
try {
|
|
246
|
-
// maximum 5 simultaneously executions
|
|
247
|
-
} finally {
|
|
248
|
-
await semaphore.release()
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### MultiSemaphore
|
|
254
|
-
|
|
255
|
-
Same as `Semaphore` with one difference - MultiSemaphore will try to acquire multiple permits instead of one.
|
|
256
|
-
|
|
257
|
-
`MultiSemaphore` and `Semaphore` shares same key namespace and can be used together (see test/src/RedisMultiSemaphore.test.ts).
|
|
258
|
-
|
|
259
|
-
##### new MultiSemaphore(redisClient, key, maxCount, permits [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])
|
|
260
|
-
|
|
261
|
-
- `redisClient` - **required**, configured `redis` client
|
|
262
|
-
- `key` - **required**, key for locking resource (final key in redis: `semaphore:<key>`)
|
|
263
|
-
- `maxCount` - **required**, maximum simultaneously resource usage count
|
|
264
|
-
- `permits` - **required**, number of acquiring permits
|
|
265
|
-
- `options` _optional_ See `Mutex` options
|
|
266
|
-
|
|
267
|
-
#### Example
|
|
268
|
-
|
|
269
|
-
```javascript
|
|
270
|
-
const MultiSemaphore = require('redis-semaphore').MultiSemaphore
|
|
271
|
-
const Redis = require('ioredis')
|
|
272
|
-
|
|
273
|
-
// TypeScript
|
|
274
|
-
// import { MultiSemaphore } from 'redis-semaphore'
|
|
275
|
-
// import Redis from 'ioredis'
|
|
276
|
-
|
|
277
|
-
const redisClient = new Redis()
|
|
278
|
-
|
|
279
|
-
async function doSomething() {
|
|
280
|
-
const semaphore = new MultiSemaphore(redisClient, 'lockingResource', 5, 2)
|
|
281
|
-
|
|
282
|
-
await semaphore.acquire()
|
|
283
|
-
try {
|
|
284
|
-
// make 2 parallel calls to remote service which allow only 5 simultaneously calls
|
|
285
|
-
} finally {
|
|
286
|
-
await semaphore.release()
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### RedlockMutex
|
|
292
|
-
|
|
293
|
-
Distributed `Mutex` version
|
|
294
|
-
|
|
295
|
-
> See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)
|
|
296
|
-
|
|
297
|
-
##### new RedlockMutex(redisClients, key [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])
|
|
298
|
-
|
|
299
|
-
- `redisClients` - **required**, array of configured `redis` client connected to independent nodes
|
|
300
|
-
- `key` - **required**, key for locking resource (final key in redis: `mutex:<key>`)
|
|
301
|
-
- `options` _optional_ See `Mutex` options
|
|
302
|
-
|
|
303
|
-
#### Example
|
|
304
|
-
|
|
305
|
-
```javascript
|
|
306
|
-
const RedlockMutex = require('redis-semaphore').RedlockMutex
|
|
307
|
-
const Redis = require('ioredis')
|
|
308
|
-
|
|
309
|
-
// TypeScript
|
|
310
|
-
// import { RedlockMutex } from 'redis-semaphore'
|
|
311
|
-
// import Redis from 'ioredis'
|
|
312
|
-
|
|
313
|
-
const redisClients = [
|
|
314
|
-
new Redis('127.0.0.1:6377'),
|
|
315
|
-
new Redis('127.0.0.1:6378'),
|
|
316
|
-
new Redis('127.0.0.1:6379')
|
|
317
|
-
] // "Those nodes are totally independent, so we don’t use replication or any other implicit coordination system."
|
|
318
|
-
|
|
319
|
-
async function doSomething() {
|
|
320
|
-
const mutex = new RedlockMutex(redisClients, 'lockingResource')
|
|
321
|
-
await mutex.acquire()
|
|
322
|
-
try {
|
|
323
|
-
// critical code
|
|
324
|
-
} finally {
|
|
325
|
-
await mutex.release()
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### RedlockSemaphore
|
|
331
|
-
|
|
332
|
-
Distributed `Semaphore` version
|
|
333
|
-
|
|
334
|
-
> See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)
|
|
335
|
-
|
|
336
|
-
##### new RedlockSemaphore(redisClients, key, maxCount [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])
|
|
337
|
-
|
|
338
|
-
- `redisClients` - **required**, array of configured `redis` client connected to independent nodes
|
|
339
|
-
- `key` - **required**, key for locking resource (final key in redis: `semaphore:<key>`)
|
|
340
|
-
- `maxCount` - **required**, maximum simultaneously resource usage count
|
|
341
|
-
- `options` _optional_ See `Mutex` options
|
|
342
|
-
|
|
343
|
-
#### Example
|
|
344
|
-
|
|
345
|
-
```javascript
|
|
346
|
-
const RedlockSemaphore = require('redis-semaphore').RedlockSemaphore
|
|
347
|
-
const Redis = require('ioredis')
|
|
348
|
-
|
|
349
|
-
// TypeScript
|
|
350
|
-
// import { RedlockSemaphore } from 'redis-semaphore'
|
|
351
|
-
// import Redis from 'ioredis'
|
|
352
|
-
|
|
353
|
-
const redisClients = [
|
|
354
|
-
new Redis('127.0.0.1:6377'),
|
|
355
|
-
new Redis('127.0.0.1:6378'),
|
|
356
|
-
new Redis('127.0.0.1:6379')
|
|
357
|
-
] // "Those nodes are totally independent, so we don’t use replication or any other implicit coordination system."
|
|
358
|
-
|
|
359
|
-
async function doSomething() {
|
|
360
|
-
const semaphore = new Semaphore(redisClients, 'lockingResource', 5)
|
|
361
|
-
await semaphore.acquire()
|
|
362
|
-
try {
|
|
363
|
-
// maximum 5 simultaneously executions
|
|
364
|
-
} finally {
|
|
365
|
-
await semaphore.release()
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### RedlockMultiSemaphore
|
|
371
|
-
|
|
372
|
-
Distributed `MultiSemaphore` version
|
|
373
|
-
|
|
374
|
-
> See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)
|
|
375
|
-
|
|
376
|
-
##### new RedlockMultiSemaphore(redisClients, key, maxCount, permits [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])
|
|
377
|
-
|
|
378
|
-
- `redisClients` - **required**, array of configured `redis` client connected to independent nodes
|
|
379
|
-
- `key` - **required**, key for locking resource (final key in redis: `semaphore:<key>`)
|
|
380
|
-
- `maxCount` - **required**, maximum simultaneously resource usage count
|
|
381
|
-
- `permits` - **required**, number of acquiring permits
|
|
382
|
-
- `options` _optional_ See `Mutex` options
|
|
383
|
-
|
|
384
|
-
#### Example
|
|
385
|
-
|
|
386
|
-
```javascript
|
|
387
|
-
const RedlockMultiSemaphore = require('redis-semaphore').RedlockMultiSemaphore
|
|
388
|
-
const Redis = require('ioredis')
|
|
389
|
-
|
|
390
|
-
// TypeScript
|
|
391
|
-
// import { RedlockMultiSemaphore } from 'redis-semaphore'
|
|
392
|
-
// import Redis from 'ioredis'
|
|
393
|
-
|
|
394
|
-
const redisClients = [
|
|
395
|
-
new Redis('127.0.0.1:6377'),
|
|
396
|
-
new Redis('127.0.0.1:6378'),
|
|
397
|
-
new Redis('127.0.0.1:6379')
|
|
398
|
-
] // "Those nodes are totally independent, so we don’t use replication or any other implicit coordination system."
|
|
399
|
-
|
|
400
|
-
async function doSomething() {
|
|
401
|
-
const semaphore = new RedlockMultiSemaphore(
|
|
402
|
-
redisClients,
|
|
403
|
-
'lockingResource',
|
|
404
|
-
5,
|
|
405
|
-
2
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
await semaphore.acquire()
|
|
409
|
-
try {
|
|
410
|
-
// make 2 parallel calls to remote service which allow only 5 simultaneously calls
|
|
411
|
-
} finally {
|
|
412
|
-
await semaphore.release()
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
## Development
|
|
418
|
-
|
|
419
|
-
```shell
|
|
420
|
-
yarn --immutable
|
|
421
|
-
./setup-redis-servers.sh
|
|
422
|
-
yarn dev
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
## License
|
|
426
|
-
|
|
427
|
-
MIT
|
|
428
|
-
|
|
429
|
-
[![FOSSA Status][fossa-large-image]][fossa-large-url]
|
|
430
|
-
|
|
431
|
-
[npm-image]: https://img.shields.io/npm/v/redis-semaphore.svg?style=flat-square
|
|
432
|
-
[npm-url]: https://npmjs.org/package/redis-semaphore
|
|
433
|
-
[ci-image]: https://github.com/swarthy/redis-semaphore/actions/workflows/branches.yml/badge.svg
|
|
434
|
-
[ci-url]: https://github.com/swarthy/redis-semaphore/actions/workflows/branches.yml
|
|
435
|
-
[codeclimate-image]: https://api.codeclimate.com/v1/badges/02778c96bb5983eb150c/maintainability
|
|
436
|
-
[codeclimate-url]: https://codeclimate.com/github/swarthy/redis-semaphore/maintainability
|
|
437
|
-
[snyk-image]: https://snyk.io/test/npm/redis-semaphore/badge.svg
|
|
438
|
-
[snyk-url]: https://snyk.io/test/npm/redis-semaphore
|
|
439
|
-
[coverage-image]: https://coveralls.io/repos/github/swarthy/redis-semaphore/badge.svg?branch=master
|
|
440
|
-
[coverage-url]: https://coveralls.io/r/swarthy/redis-semaphore?branch=master
|
|
441
|
-
[fossa-badge-image]: https://app.fossa.com/api/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git.svg?type=shield
|
|
442
|
-
[fossa-badge-url]: https://app.fossa.com/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git?ref=badge_shield
|
|
443
|
-
[fossa-large-image]: https://app.fossa.com/api/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git.svg?type=large
|
|
444
|
-
[fossa-large-url]: https://app.fossa.com/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git?ref=badge_large
|
|
445
|
-
[typescript-image]: https://badgen.net/npm/types/tslib
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
version: '3.7'
|
|
2
|
-
|
|
3
|
-
services:
|
|
4
|
-
waiter:
|
|
5
|
-
image: node:alpine
|
|
6
|
-
volumes:
|
|
7
|
-
- ./:/app
|
|
8
|
-
working_dir: /app
|
|
9
|
-
command: >
|
|
10
|
-
sh -c "
|
|
11
|
-
corepack enable &&
|
|
12
|
-
yarn wait-for --redis redis://redis1 &&
|
|
13
|
-
yarn wait-for --redis redis://redis2 &&
|
|
14
|
-
yarn wait-for --redis redis://redis3 &&
|
|
15
|
-
echo 'All redis instances ready!'
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
redis1:
|
|
19
|
-
image: redis:alpine
|
|
20
|
-
ports:
|
|
21
|
-
- 6001:6379
|
|
22
|
-
|
|
23
|
-
redis2:
|
|
24
|
-
image: redis:alpine
|
|
25
|
-
ports:
|
|
26
|
-
- 6002:6379
|
|
27
|
-
|
|
28
|
-
redis3:
|
|
29
|
-
image: redis:alpine
|
|
30
|
-
ports:
|
|
31
|
-
- 6003:6379
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import typescript from '@typescript-eslint/eslint-plugin'
|
|
2
|
-
import typescriptParser from '@typescript-eslint/parser'
|
|
3
|
-
import nodePlugin from 'eslint-plugin-node'
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
{
|
|
7
|
-
ignores: ['lib/**', 'es/**', 'coverage/**', '.nyc_output/**']
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
files: ['**/*.ts'],
|
|
11
|
-
languageOptions: {
|
|
12
|
-
parser: typescriptParser,
|
|
13
|
-
parserOptions: {
|
|
14
|
-
ecmaVersion: 2024,
|
|
15
|
-
sourceType: 'module',
|
|
16
|
-
project: './tsconfig.json',
|
|
17
|
-
ecmaFeatures: {
|
|
18
|
-
jsx: false
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
globals: {
|
|
22
|
-
console: true,
|
|
23
|
-
process: true,
|
|
24
|
-
setTimeout: true,
|
|
25
|
-
clearTimeout: true,
|
|
26
|
-
setInterval: true,
|
|
27
|
-
clearInterval: true
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
plugins: {
|
|
31
|
-
'@typescript-eslint': typescript
|
|
32
|
-
},
|
|
33
|
-
rules: {
|
|
34
|
-
...typescript.configs['recommended'].rules,
|
|
35
|
-
...typescript.configs['recommended-requiring-type-checking'].rules,
|
|
36
|
-
'@typescript-eslint/explicit-function-return-type': 'error',
|
|
37
|
-
'@typescript-eslint/no-explicit-any': 'error',
|
|
38
|
-
'@typescript-eslint/no-unused-vars': [
|
|
39
|
-
'error',
|
|
40
|
-
{ argsIgnorePattern: '^_' }
|
|
41
|
-
],
|
|
42
|
-
'@typescript-eslint/no-unused-expressions': 'off',
|
|
43
|
-
'@typescript-eslint/no-misused-promises': 'off',
|
|
44
|
-
'@typescript-eslint/require-await': 'off',
|
|
45
|
-
'no-unused-expressions': 'off'
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
files: ['test/**/*.ts'],
|
|
50
|
-
languageOptions: {
|
|
51
|
-
globals: {
|
|
52
|
-
describe: true,
|
|
53
|
-
it: true,
|
|
54
|
-
before: true,
|
|
55
|
-
after: true,
|
|
56
|
-
beforeEach: true,
|
|
57
|
-
afterEach: true,
|
|
58
|
-
mocha: true
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
rules: {
|
|
62
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
63
|
-
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
64
|
-
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
65
|
-
'@typescript-eslint/no-unsafe-call': 'off',
|
|
66
|
-
'@typescript-eslint/no-unsafe-return': 'off',
|
|
67
|
-
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
68
|
-
'@typescript-eslint/await-thenable': 'off',
|
|
69
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
70
|
-
'no-unused-vars': 'off'
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
]
|