redis-semaphore
Advanced tools
Comparing version
@@ -0,1 +1,6 @@ | ||
### redis-semaphore@4.1.0 | ||
- Added `.isAcquired` property on all locks | ||
- Added `onLostLock` constructor option. By default throws unhandled error. | ||
### redis-semaphore@4.0.0 | ||
@@ -2,0 +7,0 @@ |
@@ -7,3 +7,4 @@ import MultiSemaphore from './RedisMultiSemaphore'; | ||
import RedlockSemaphore from './RedlockSemaphore'; | ||
export * from './misc'; | ||
export { defaultTimeoutOptions } from './misc'; | ||
export { Mutex, Semaphore, MultiSemaphore, RedlockMutex, RedlockSemaphore, RedlockMultiSemaphore }; | ||
export type { LockLostCallback, TimeoutOptions, LockOptions } from './types'; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -16,3 +6,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RedlockMultiSemaphore = exports.RedlockSemaphore = exports.RedlockMutex = exports.MultiSemaphore = exports.Semaphore = exports.Mutex = void 0; | ||
exports.RedlockMultiSemaphore = exports.RedlockSemaphore = exports.RedlockMutex = exports.MultiSemaphore = exports.Semaphore = exports.Mutex = exports.defaultTimeoutOptions = void 0; | ||
const RedisMultiSemaphore_1 = __importDefault(require("./RedisMultiSemaphore")); | ||
@@ -30,3 +20,4 @@ exports.MultiSemaphore = RedisMultiSemaphore_1.default; | ||
exports.RedlockSemaphore = RedlockSemaphore_1.default; | ||
__exportStar(require("./misc"), exports); | ||
var misc_1 = require("./misc"); | ||
Object.defineProperty(exports, "defaultTimeoutOptions", { enumerable: true, get: function () { return misc_1.defaultTimeoutOptions; } }); | ||
//# sourceMappingURL=index.js.map |
/// <reference types="node" /> | ||
import { TimeoutOptions } from './misc'; | ||
import { LockLostCallback, LockOptions } from './types'; | ||
interface AcquireOptions { | ||
@@ -17,7 +17,10 @@ identifier: string; | ||
protected _refreshing: boolean; | ||
protected _acquired: boolean; | ||
protected _onLockLost: LockLostCallback; | ||
protected abstract _refresh(): Promise<boolean>; | ||
protected abstract _acquire(): Promise<boolean>; | ||
protected abstract _release(): Promise<void>; | ||
constructor({ lockTimeout, acquireTimeout, retryInterval, refreshInterval }?: TimeoutOptions); | ||
constructor({ lockTimeout, acquireTimeout, retryInterval, refreshInterval, onLockLost }?: LockOptions); | ||
get identifier(): string; | ||
get isAcquired(): boolean; | ||
private _startRefresh; | ||
@@ -24,0 +27,0 @@ private _stopRefresh; |
@@ -15,4 +15,5 @@ "use strict"; | ||
class Lock { | ||
constructor({ lockTimeout = misc_1.defaultTimeoutOptions.lockTimeout, acquireTimeout = misc_1.defaultTimeoutOptions.acquireTimeout, retryInterval = misc_1.defaultTimeoutOptions.retryInterval, refreshInterval = Math.round(lockTimeout * REFRESH_INTERVAL_COEF) } = misc_1.defaultTimeoutOptions) { | ||
constructor({ lockTimeout = misc_1.defaultTimeoutOptions.lockTimeout, acquireTimeout = misc_1.defaultTimeoutOptions.acquireTimeout, retryInterval = misc_1.defaultTimeoutOptions.retryInterval, refreshInterval = Math.round(lockTimeout * REFRESH_INTERVAL_COEF), onLockLost = misc_1.defaultOnLockLost } = misc_1.defaultTimeoutOptions) { | ||
this._refreshing = false; | ||
this._acquired = false; | ||
this._identifier = uuid_1.v4(); | ||
@@ -27,2 +28,3 @@ this._acquireOptions = { | ||
this._processRefresh = this._processRefresh.bind(this); | ||
this._onLockLost = onLockLost; | ||
} | ||
@@ -32,2 +34,5 @@ get identifier() { | ||
} | ||
get isAcquired() { | ||
return this._acquired; | ||
} | ||
_startRefresh() { | ||
@@ -53,4 +58,6 @@ this._refreshInterval = setInterval(this._processRefresh, this._refreshTimeInterval); | ||
if (!refreshed) { | ||
this._acquired = false; | ||
this._stopRefresh(); | ||
throw new LostLockError_1.default(`Lost ${this._kind} for key ${this._key}`); | ||
const lockLostError = new LostLockError_1.default(`Lost ${this._kind} for key ${this._key}`); | ||
this._onLockLost(lockLostError); | ||
} | ||
@@ -68,2 +75,3 @@ } | ||
} | ||
this._acquired = true; | ||
if (this._refreshTimeInterval > 0) { | ||
@@ -79,2 +87,3 @@ this._startRefresh(); | ||
await this._release(); | ||
this._acquired = false; | ||
} | ||
@@ -81,0 +90,0 @@ } |
@@ -1,7 +0,2 @@ | ||
export interface TimeoutOptions { | ||
lockTimeout?: number; | ||
acquireTimeout?: number; | ||
retryInterval?: number; | ||
refreshInterval?: number; | ||
} | ||
import LostLockError from '../src/errors/LostLockError'; | ||
export declare const defaultTimeoutOptions: { | ||
@@ -12,1 +7,2 @@ lockTimeout: number; | ||
}; | ||
export declare function defaultOnLockLost(err: LostLockError): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultTimeoutOptions = void 0; | ||
exports.defaultOnLockLost = exports.defaultTimeoutOptions = void 0; | ||
exports.defaultTimeoutOptions = { | ||
@@ -9,2 +9,6 @@ lockTimeout: 10000, | ||
}; | ||
function defaultOnLockLost(err) { | ||
throw err; | ||
} | ||
exports.defaultOnLockLost = defaultOnLockLost; | ||
//# sourceMappingURL=misc.js.map |
import Redis from 'ioredis'; | ||
import { TimeoutOptions } from './misc'; | ||
import RedisSemaphore from './RedisSemaphore'; | ||
import { LockOptions } from './types'; | ||
export default class RedisMultiSemaphore extends RedisSemaphore { | ||
protected _kind: string; | ||
protected _permits: number; | ||
constructor(client: Redis.Redis, key: string, limit: number, permits: number, options?: TimeoutOptions); | ||
constructor(client: Redis.Redis, key: string, limit: number, permits: number, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -9,0 +9,0 @@ protected _acquire(): Promise<boolean>; |
import Redis from 'ioredis'; | ||
import { Lock } from './Lock'; | ||
import { TimeoutOptions } from './misc'; | ||
import { LockOptions } from './types'; | ||
export default class RedisMutex extends Lock { | ||
@@ -8,3 +8,3 @@ protected _kind: string; | ||
protected _client: Redis.Redis; | ||
constructor(client: Redis.Redis, key: string, options?: TimeoutOptions); | ||
constructor(client: Redis.Redis, key: string, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -11,0 +11,0 @@ protected _acquire(): Promise<boolean>; |
import Redis from 'ioredis'; | ||
import { TimeoutOptions } from './misc'; | ||
import RedisMutex from './RedisMutex'; | ||
import { LockOptions } from './types'; | ||
export default class RedisSemaphore extends RedisMutex { | ||
protected _kind: string; | ||
protected _limit: number; | ||
constructor(client: Redis.Redis, key: string, limit: number, options?: TimeoutOptions); | ||
constructor(client: Redis.Redis, key: string, limit: number, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -9,0 +9,0 @@ protected _acquire(): Promise<boolean>; |
import Redis from 'ioredis'; | ||
import { TimeoutOptions } from './misc'; | ||
import RedlockSemaphore from './RedlockSemaphore'; | ||
import { LockOptions } from './types'; | ||
export default class RedlockMultiSemaphore extends RedlockSemaphore { | ||
protected _kind: string; | ||
protected _permits: number; | ||
constructor(clients: Redis.Redis[], key: string, limit: number, permits: number, options?: TimeoutOptions); | ||
constructor(clients: Redis.Redis[], key: string, limit: number, permits: number, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -9,0 +9,0 @@ protected _acquire(): Promise<boolean>; |
import Redis from 'ioredis'; | ||
import { Lock } from './Lock'; | ||
import { TimeoutOptions } from './misc'; | ||
import { LockOptions } from './types'; | ||
export default class RedlockMutex extends Lock { | ||
@@ -8,3 +8,3 @@ protected _kind: string; | ||
protected _clients: Redis.Redis[]; | ||
constructor(clients: Redis.Redis[], key: string, options?: TimeoutOptions); | ||
constructor(clients: Redis.Redis[], key: string, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -11,0 +11,0 @@ protected _acquire(): Promise<boolean>; |
import Redis from 'ioredis'; | ||
import { TimeoutOptions } from './misc'; | ||
import RedlockMutex from './RedlockMutex'; | ||
import { LockOptions } from './types'; | ||
export default class RedlockSemaphore extends RedlockMutex { | ||
protected _kind: string; | ||
protected _limit: number; | ||
constructor(clients: Redis.Redis[], key: string, limit: number, options?: TimeoutOptions); | ||
constructor(clients: Redis.Redis[], key: string, limit: number, options?: LockOptions); | ||
protected _refresh(): Promise<boolean>; | ||
@@ -9,0 +9,0 @@ protected _acquire(): Promise<boolean>; |
@@ -1,5 +0,14 @@ | ||
export interface Lockable { | ||
identifier: string; | ||
acquire(): Promise<void>; | ||
release(): Promise<void>; | ||
import LostLockError from './errors/LostLockError'; | ||
import { Lock } from './Lock'; | ||
export interface LockLostCallback { | ||
(this: Lock, err: LostLockError): void; | ||
} | ||
export interface TimeoutOptions { | ||
lockTimeout?: number; | ||
acquireTimeout?: number; | ||
retryInterval?: number; | ||
refreshInterval?: number; | ||
} | ||
export interface LockOptions extends TimeoutOptions { | ||
onLockLost?: LockLostCallback; | ||
} |
{ | ||
"name": "redis-semaphore", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "Distributed mutex and semaphore based on Redis", | ||
@@ -34,10 +34,10 @@ "main": "lib/index.js", | ||
"@types/debug": "^4.1.5", | ||
"@types/ioredis": "^4.17.10", | ||
"@types/ioredis": "^4.19.3", | ||
"@types/mocha": "^8.2.0", | ||
"@types/node": "14.14.20", | ||
"@types/node": "14.14.22", | ||
"@types/sinon": "^9.0.10", | ||
"@types/sinon-chai": "^3.2.5", | ||
"@types/uuid": "^8.3.0", | ||
"@typescript-eslint/eslint-plugin": "4.12.0", | ||
"@typescript-eslint/parser": "4.12.0", | ||
"@typescript-eslint/eslint-plugin": "4.14.1", | ||
"@typescript-eslint/parser": "4.14.1", | ||
"babel-eslint": "^10.1.0", | ||
@@ -48,3 +48,3 @@ "benchmark": "^2.1.4", | ||
"coveralls": "^3.1.0", | ||
"eslint": "7.17.0", | ||
"eslint": "7.18.0", | ||
"eslint-config-inclusive": "1.2.10", | ||
@@ -56,5 +56,5 @@ "eslint-plugin-node": "11.1.0", | ||
"nyc": "^15.1.0", | ||
"sinon": "9.2.2", | ||
"sinon": "9.2.4", | ||
"sinon-chai": "3.5.0", | ||
"snyk": "1.437.3", | ||
"snyk": "1.440.1", | ||
"ts-node": "^9.1.1", | ||
@@ -61,0 +61,0 @@ "typescript": "^4.1.3" |
@@ -36,7 +36,8 @@ # redis-semaphore | ||
- `key` - **required**, key for locking resource (final key in redis: `mutex:<key>`) | ||
- `timeouts` _optional_ | ||
- `lockTimeout` - ms, time after mutex will be auto released (expired) | ||
- `acquireTimeout` - ms, max timeout for `.acquire()` call | ||
- `retryInterval` - ms, time between acquire attempts if resource locked | ||
- `refreshInterval` - ms, auto-refresh interval | ||
- `options` - _optional_ | ||
- `lockTimeout` - _optional_ ms, time after mutex will be auto released (expired) | ||
- `acquireTimeout` - _optional_ ms, max timeout for `.acquire()` call | ||
- `retryInterval` - _optional_ ms, time between acquire attempts if resource locked | ||
- `refreshInterval` - _optional_ ms, auto-refresh interval; to disable auto-refresh behaviour set `0` | ||
- `onLockLost` - _optional_ function, called when lock loss is detected due refresh cycle; default onLockLost throws unhandled LostLockError | ||
@@ -66,2 +67,24 @@ #### Example | ||
#### Example with lost lock handling | ||
```javascript | ||
async function doSomething() { | ||
const mutex = new Mutex(redisClient, 'lockingResource', { | ||
// By default onLockLost throws unhandled LostLockError | ||
onLockLost(err) { | ||
console.error(err) | ||
} | ||
}) | ||
await mutex.acquire() | ||
try { | ||
while (mutex.isAcquired) { | ||
// critical cycle iteration | ||
} | ||
} finally { | ||
// It's safe to always call release, because if lock is no longer belongs to this mutex, .release() will have no effect | ||
await mutex.release() | ||
} | ||
} | ||
``` | ||
### Semaphore | ||
@@ -84,3 +107,3 @@ | ||
- `maxCount` - **required**, maximum simultaneously resource usage count | ||
- `timeouts` _optional_ See `Mutex` timeouts | ||
- `options` _optional_ See `Mutex` options | ||
@@ -122,3 +145,3 @@ #### Example | ||
- `permits` - **required**, number of acquiring permits | ||
- `timeouts` _optional_ See `Mutex` timeouts | ||
- `options` _optional_ See `Mutex` options | ||
@@ -159,3 +182,3 @@ #### Example | ||
- `key` - **required**, key for locking resource (final key in redis: `mutex:<key>`) | ||
- `timeouts` _optional_ See `Mutex` timeouts | ||
- `options` _optional_ See `Mutex` options | ||
@@ -200,3 +223,3 @@ #### Example | ||
- `maxCount` - **required**, maximum simultaneously resource usage count | ||
- `timeouts` _optional_ See `Mutex` timeouts | ||
- `options` _optional_ See `Mutex` options | ||
@@ -242,3 +265,3 @@ #### Example | ||
- `permits` - **required**, number of acquiring permits | ||
- `timeouts` _optional_ See `Mutex` timeouts | ||
- `options` _optional_ See `Mutex` options | ||
@@ -245,0 +268,0 @@ #### Example |
@@ -8,3 +8,3 @@ import MultiSemaphore from './RedisMultiSemaphore' | ||
export * from './misc' | ||
export { defaultTimeoutOptions } from './misc' | ||
@@ -19,1 +19,3 @@ export { | ||
} | ||
export type { LockLostCallback, TimeoutOptions, LockOptions } from './types' |
@@ -6,3 +6,4 @@ import createDebug from 'debug' | ||
import TimeoutError from './errors/TimeoutError' | ||
import { defaultTimeoutOptions, TimeoutOptions } from './misc' | ||
import { defaultOnLockLost, defaultTimeoutOptions } from './misc' | ||
import { LockLostCallback, LockOptions } from './types' | ||
@@ -28,2 +29,4 @@ const REFRESH_INTERVAL_COEF = 0.8 | ||
protected _refreshing = false | ||
protected _acquired = false | ||
protected _onLockLost: LockLostCallback | ||
@@ -38,4 +41,5 @@ protected abstract _refresh(): Promise<boolean> | ||
retryInterval = defaultTimeoutOptions.retryInterval, | ||
refreshInterval = Math.round(lockTimeout * REFRESH_INTERVAL_COEF) | ||
}: TimeoutOptions = defaultTimeoutOptions) { | ||
refreshInterval = Math.round(lockTimeout * REFRESH_INTERVAL_COEF), | ||
onLockLost = defaultOnLockLost | ||
}: LockOptions = defaultTimeoutOptions) { | ||
this._identifier = uuid4() | ||
@@ -50,2 +54,3 @@ this._acquireOptions = { | ||
this._processRefresh = this._processRefresh.bind(this) | ||
this._onLockLost = onLockLost | ||
} | ||
@@ -57,2 +62,6 @@ | ||
get isAcquired() { | ||
return this._acquired | ||
} | ||
private _startRefresh() { | ||
@@ -89,4 +98,8 @@ this._refreshInterval = setInterval( | ||
if (!refreshed) { | ||
this._acquired = false | ||
this._stopRefresh() | ||
throw new LostLockError(`Lost ${this._kind} for key ${this._key}`) | ||
const lockLostError = new LostLockError( | ||
`Lost ${this._kind} for key ${this._key}` | ||
) | ||
this._onLockLost(lockLostError) | ||
} | ||
@@ -104,2 +117,3 @@ } finally { | ||
} | ||
this._acquired = true | ||
if (this._refreshTimeInterval > 0) { | ||
@@ -118,3 +132,4 @@ this._startRefresh() | ||
await this._release() | ||
this._acquired = false | ||
} | ||
} |
@@ -1,7 +0,2 @@ | ||
export interface TimeoutOptions { | ||
lockTimeout?: number | ||
acquireTimeout?: number | ||
retryInterval?: number | ||
refreshInterval?: number | ||
} | ||
import LostLockError from '../src/errors/LostLockError' | ||
@@ -13,1 +8,5 @@ export const defaultTimeoutOptions = { | ||
} | ||
export function defaultOnLockLost(err: LostLockError) { | ||
throw err | ||
} |
import Redis from 'ioredis' | ||
import { TimeoutOptions } from './misc' | ||
import { acquireSemaphore } from './multiSemaphore/acquire/index' | ||
@@ -8,2 +7,3 @@ import { refreshSemaphore } from './multiSemaphore/refresh/index' | ||
import RedisSemaphore from './RedisSemaphore' | ||
import { LockOptions } from './types' | ||
@@ -19,3 +19,3 @@ export default class RedisMultiSemaphore extends RedisSemaphore { | ||
permits: number, | ||
options?: TimeoutOptions | ||
options?: LockOptions | ||
) { | ||
@@ -22,0 +22,0 @@ super(client, key, limit, options) |
import Redis from 'ioredis' | ||
import { Lock } from './Lock' | ||
import { TimeoutOptions } from './misc' | ||
import { acquireMutex } from './mutex/acquire' | ||
import { refreshMutex } from './mutex/refresh' | ||
import { releaseMutex } from './mutex/release' | ||
import { LockOptions } from './types' | ||
@@ -14,3 +14,3 @@ export default class RedisMutex extends Lock { | ||
constructor(client: Redis.Redis, key: string, options?: TimeoutOptions) { | ||
constructor(client: Redis.Redis, key: string, options?: LockOptions) { | ||
super(options) | ||
@@ -17,0 +17,0 @@ if (!client) { |
import Redis from 'ioredis' | ||
import { TimeoutOptions } from './misc' | ||
import RedisMutex from './RedisMutex' | ||
@@ -8,2 +7,3 @@ import { acquireSemaphore } from './semaphore/acquire/index' | ||
import { releaseSemaphore } from './semaphore/release' | ||
import { LockOptions } from './types' | ||
@@ -18,3 +18,3 @@ export default class RedisSemaphore extends RedisMutex { | ||
limit: number, | ||
options?: TimeoutOptions | ||
options?: LockOptions | ||
) { | ||
@@ -21,0 +21,0 @@ super(client, key, options) |
import Redis from 'ioredis' | ||
import { TimeoutOptions } from './misc' | ||
import { acquireRedlockMultiSemaphore } from './redlockMultiSemaphore/acquire' | ||
@@ -8,2 +7,3 @@ import { refreshRedlockMultiSemaphore } from './redlockMultiSemaphore/refresh' | ||
import RedlockSemaphore from './RedlockSemaphore' | ||
import { LockOptions } from './types' | ||
@@ -19,3 +19,3 @@ export default class RedlockMultiSemaphore extends RedlockSemaphore { | ||
permits: number, | ||
options?: TimeoutOptions | ||
options?: LockOptions | ||
) { | ||
@@ -22,0 +22,0 @@ super(clients, key, limit, options) |
import Redis from 'ioredis' | ||
import { Lock } from './Lock' | ||
import { defaultTimeoutOptions, TimeoutOptions } from './misc' | ||
import { defaultTimeoutOptions } from './misc' | ||
import { acquireRedlockMutex } from './redlockMutex/acquire' | ||
import { refreshRedlockMutex } from './redlockMutex/refresh' | ||
import { releaseRedlockMutex } from './redlockMutex/release' | ||
import { LockOptions } from './types' | ||
@@ -17,3 +18,3 @@ export default class RedlockMutex extends Lock { | ||
key: string, | ||
options: TimeoutOptions = defaultTimeoutOptions | ||
options: LockOptions = defaultTimeoutOptions | ||
) { | ||
@@ -20,0 +21,0 @@ super(options) |
import Redis from 'ioredis' | ||
import { TimeoutOptions } from './misc' | ||
import RedlockMutex from './RedlockMutex' | ||
@@ -8,2 +7,3 @@ import { acquireRedlockSemaphore } from './redlockSemaphore/acquire' | ||
import { releaseRedlockSemaphore } from './redlockSemaphore/release' | ||
import { LockOptions } from './types' | ||
@@ -18,3 +18,3 @@ export default class RedlockSemaphore extends RedlockMutex { | ||
limit: number, | ||
options?: TimeoutOptions | ||
options?: LockOptions | ||
) { | ||
@@ -21,0 +21,0 @@ super(clients, key, options) |
@@ -1,5 +0,17 @@ | ||
export interface Lockable { | ||
identifier: string | ||
acquire(): Promise<void> | ||
release(): Promise<void> | ||
import LostLockError from './errors/LostLockError' | ||
import { Lock } from './Lock' | ||
export interface LockLostCallback { | ||
(this: Lock, err: LostLockError): void | ||
} | ||
export interface TimeoutOptions { | ||
lockTimeout?: number | ||
acquireTimeout?: number | ||
retryInterval?: number | ||
refreshInterval?: number | ||
} | ||
export interface LockOptions extends TimeoutOptions { | ||
onLockLost?: LockLostCallback | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
126857
0.27%2338
0.34%317
7.82%157
-7.65%