murlock 1.0.1 → 1.1.1
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/LICENSE +1 -1
- package/README.md +38 -8
- package/dist/constants/index.js.map +1 -1
- package/dist/decorators/murlock.decorator.d.ts +0 -1
- package/dist/decorators/murlock.decorator.js +54 -26
- package/dist/decorators/murlock.decorator.js.map +1 -1
- package/dist/interfaces/murlock-options.interface.d.ts +1 -0
- package/dist/lua/lock.lua +9 -0
- package/dist/lua/unlock.lua +8 -0
- package/dist/murlock.module.js +2 -0
- package/dist/murlock.module.js.map +1 -1
- package/dist/murlock.service.d.ts +11 -3
- package/dist/murlock.service.js +54 -16
- package/dist/murlock.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/dist/decorators/lock.decorator.d.ts +0 -1
- package/dist/decorators/lock.decorator.js +0 -11
- package/dist/decorators/lock.decorator.js.map +0 -1
- package/dist/interceptors/index.d.ts +0 -1
- package/dist/interceptors/index.js +0 -18
- package/dist/interceptors/index.js.map +0 -1
- package/dist/interceptors/lock.interceptor.d.ts +0 -12
- package/dist/interceptors/lock.interceptor.js +0 -66
- package/dist/interceptors/lock.interceptor.js.map +0 -1
- package/dist/interfaces/lock-meta-data.interface.d.ts +0 -4
- package/dist/interfaces/lock-meta-data.interface.js +0 -3
- package/dist/interfaces/lock-meta-data.interface.js.map +0 -1
- package/dist/interfaces/lock-options.interface.d.ts +0 -4
- package/dist/interfaces/lock-options.interface.js +0 -3
- package/dist/interfaces/lock-options.interface.js.map +0 -1
- package/dist/lock.module.d.ts +0 -5
- package/dist/lock.module.js +0 -40
- package/dist/lock.module.js.map +0 -1
- package/dist/lock.service.d.ts +0 -9
- package/dist/lock.service.js +0 -58
- package/dist/lock.service.js.map +0 -1
- package/dist/lock.spec.d.ts +0 -1
- package/dist/lock.spec.js +0 -53
- package/dist/lock.spec.js.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ MurLock is a distributed lock solution designed for the NestJS framework. It pro
|
|
|
7
7
|
- **Redis-Based**: Implements a fast and effective lock mechanism using Redis.
|
|
8
8
|
- **Parameter-Based Locking**: Creates locks based on request parameters or bodies.
|
|
9
9
|
- **Highly Customizable**: Customize many parameters, such as lock duration.
|
|
10
|
-
- **
|
|
10
|
+
- **Retry Mechanism**: Implements an exponential back-off strategy if the lock is not obtained.
|
|
11
|
+
- **Logging: Provides**: logging options for debugging and monitoring.
|
|
11
12
|
- **OOP and Generic Structure**: Easily integratable and expandable due to its OOP and generic design.
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
@@ -33,6 +34,7 @@ import { MurLockModule } from 'murlock';
|
|
|
33
34
|
redisOptions: { host: 'localhost', port: 6379 },
|
|
34
35
|
wait: 1000,
|
|
35
36
|
maxAttempts: 3,
|
|
37
|
+
logLevel: 'log',
|
|
36
38
|
}),
|
|
37
39
|
],
|
|
38
40
|
})
|
|
@@ -72,6 +74,7 @@ import { MurLockModule } from 'murlock';
|
|
|
72
74
|
redisOptions: configService.get('REDIS_OPTIONS'),
|
|
73
75
|
wait: configService.get('MURLOCK_WAIT'),
|
|
74
76
|
maxAttempts: configService.get('MURLOCK_MAX_ATTEMPTS'),
|
|
77
|
+
logLevel: configService.get('LOG_LEVEL')
|
|
75
78
|
}),
|
|
76
79
|
inject: [ConfigService],
|
|
77
80
|
}),
|
|
@@ -86,17 +89,38 @@ For more details on usage and configuration, please refer to the API documentati
|
|
|
86
89
|
|
|
87
90
|
## API Documentation
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
### MurLock(releaseTime: number, ...keyParams: string[])
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
A method decorator to indicate that a particular method should be locked.
|
|
92
95
|
|
|
93
|
-
- `
|
|
96
|
+
- `releaseTime`: Time in milliseconds after which the lock should be automatically released.
|
|
97
|
+
- `...keyParams`: Method parameters based on which the lock should be made. The format is `paramName.attribute`. If just `paramName` is provided, it will use the `toString` method of that parameter.
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
### Configuration Options
|
|
96
100
|
|
|
97
|
-
-
|
|
101
|
+
- **redisOptions:** Configuration options for the Redis client.
|
|
102
|
+
- **wait:** Time (in milliseconds) to wait before retrying if a lock isn't obtained.
|
|
103
|
+
- **maxAttempts:** Maximum number of attempts to obtain a lock.
|
|
104
|
+
- **logLevel:** Logging level. Can be one of 'none', 'error', 'warn', 'log', or 'debug'.
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
### MurLockService
|
|
107
|
+
|
|
108
|
+
A NestJS injectable service to interact with the locking mechanism directly.
|
|
109
|
+
|
|
110
|
+
## Best Practices
|
|
111
|
+
|
|
112
|
+
- **Short-lived Locks:** Ensure that locks are short-lived to prevent deadlocks and increase the efficiency of your application.
|
|
113
|
+
- **Error Handling:** Always handle errors gracefully. If a lock isn't obtained, it's often better to return a failure or retry after some time.
|
|
114
|
+
- **Logging:** Adjust the `logLevel` based on your environment. Use 'debug' for development and 'error' or 'warn' for production.
|
|
115
|
+
|
|
116
|
+
## Limitations
|
|
117
|
+
|
|
118
|
+
- **Redis Persistence:** Ensure that your Redis instance has RDB persistence enabled. This ensures that in case of a crash, locks are not lost.
|
|
119
|
+
- **Single Redis Instance:** MurLock is not designed to work with Redis cluster mode. It's essential to ensure that locks are always set to a single instance.
|
|
120
|
+
|
|
121
|
+
## Contributions & Support
|
|
122
|
+
|
|
123
|
+
We welcome contributions! Please see our contributing guide for more information. For support, raise an issue on our GitHub repository.
|
|
100
124
|
|
|
101
125
|
## License
|
|
102
126
|
|
|
@@ -104,4 +128,10 @@ This project is licensed under the [MIT License](LICENSE_FILE_URL).
|
|
|
104
128
|
|
|
105
129
|
## Contact
|
|
106
130
|
|
|
107
|
-
If you have any questions or feedback, feel free to contact me at ozmen.eyupfurkan@gmail.com.
|
|
131
|
+
If you have any questions or feedback, feel free to contact me at ozmen.eyupfurkan@gmail.com.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
We hope you find MurLock useful in your projects. Don't forget to star our repo if you find it helpful!
|
|
136
|
+
|
|
137
|
+
Happy coding! 🚀
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/constants/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/constants/index.ts"],"names":[],"mappings":";;;AAGa,QAAA,oBAAoB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;AAKtD,QAAA,4BAA4B,GAAG,MAAM,CAAC,8BAA8B,CAAC,CAAC"}
|
|
@@ -10,10 +10,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.MurLock = void 0;
|
|
13
|
-
require("reflect-metadata");
|
|
14
|
-
const murlock_service_1 = require("../murlock.service");
|
|
15
13
|
const constants_1 = require("../constants");
|
|
16
14
|
const exceptions_1 = require("../exceptions");
|
|
15
|
+
const murlock_service_1 = require("../murlock.service");
|
|
17
16
|
function getParameterNames(func) {
|
|
18
17
|
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
|
19
18
|
const ARGUMENT_NAMES = /([^\s,]+)/g;
|
|
@@ -25,42 +24,71 @@ function MurLock(releaseTime, ...keyParams) {
|
|
|
25
24
|
return (target, propertyKey, descriptor) => {
|
|
26
25
|
const originalMethod = descriptor.value;
|
|
27
26
|
const methodParameterNames = getParameterNames(originalMethod);
|
|
27
|
+
function constructLockKey(args) {
|
|
28
|
+
const lockKeyElements = [
|
|
29
|
+
target.constructor.name,
|
|
30
|
+
propertyKey,
|
|
31
|
+
...keyParams.map((keyParam) => {
|
|
32
|
+
const [source, path] = keyParam.split('.');
|
|
33
|
+
const parameterIndex = methodParameterNames.indexOf(source);
|
|
34
|
+
if (parameterIndex >= 0) {
|
|
35
|
+
const parameterValue = args[parameterIndex];
|
|
36
|
+
if (typeof parameterValue === 'undefined' || parameterValue === null) {
|
|
37
|
+
throw new Error(`Parameter ${source} is undefined or null.`);
|
|
38
|
+
}
|
|
39
|
+
if (path && typeof parameterValue === 'object' && parameterValue !== null && path in parameterValue) {
|
|
40
|
+
return parameterValue[path];
|
|
41
|
+
}
|
|
42
|
+
return parameterValue instanceof Object ? parameterValue.toString() : parameterValue;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Parameter ${source} not found in method arguments.`);
|
|
45
|
+
}),
|
|
46
|
+
];
|
|
47
|
+
return lockKeyElements.join(':');
|
|
48
|
+
}
|
|
28
49
|
descriptor.value = function (...args) {
|
|
29
50
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
const
|
|
31
|
-
target.constructor.name,
|
|
32
|
-
propertyKey,
|
|
33
|
-
...keyParams.map((keyParam) => {
|
|
34
|
-
const [source, path] = keyParam.split('.');
|
|
35
|
-
const parameterIndex = methodParameterNames.indexOf(source);
|
|
36
|
-
if (parameterIndex >= 0) {
|
|
37
|
-
const parameterValue = args[parameterIndex];
|
|
38
|
-
if (path && typeof parameterValue === 'object' && parameterValue !== null && path in parameterValue) {
|
|
39
|
-
return parameterValue[path];
|
|
40
|
-
}
|
|
41
|
-
return parameterValue instanceof Object ? parameterValue.toString() : parameterValue;
|
|
42
|
-
}
|
|
43
|
-
}),
|
|
44
|
-
];
|
|
45
|
-
const lockKey = lockKeyElements.join(':');
|
|
51
|
+
const lockKey = constructLockKey(args);
|
|
46
52
|
const murLockService = Reflect.getMetadata(constants_1.MURLOCK_SERVICE_METADATA_KEY, murlock_service_1.MurLockService);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw new exceptions_1.MurLockException('Could not obtain lock');
|
|
53
|
+
if (!murLockService) {
|
|
54
|
+
throw new Error('MurLockService is not available.');
|
|
50
55
|
}
|
|
51
|
-
|
|
56
|
+
yield acquireLock(lockKey, murLockService, releaseTime);
|
|
52
57
|
try {
|
|
53
|
-
|
|
58
|
+
return yield originalMethod.apply(this, args);
|
|
54
59
|
}
|
|
55
60
|
finally {
|
|
56
|
-
yield
|
|
61
|
+
yield releaseLock(lockKey, murLockService);
|
|
57
62
|
}
|
|
58
|
-
return result;
|
|
59
63
|
});
|
|
60
64
|
};
|
|
61
|
-
Reflect.defineMetadata(
|
|
65
|
+
Reflect.defineMetadata(constants_1.MURLOCK_KEY_METADATA, { releaseTime, keyParams }, descriptor.value);
|
|
62
66
|
return descriptor;
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
exports.MurLock = MurLock;
|
|
70
|
+
function acquireLock(lockKey, murLockService, releaseTime) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
let isLockSuccessful = false;
|
|
73
|
+
try {
|
|
74
|
+
isLockSuccessful = yield murLockService.lock(lockKey, releaseTime);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new exceptions_1.MurLockException(`Failed to acquire lock for key ${lockKey}: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
if (!isLockSuccessful) {
|
|
80
|
+
throw new exceptions_1.MurLockException(`Could not obtain lock for key ${lockKey}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function releaseLock(lockKey, murLockService) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
try {
|
|
87
|
+
yield murLockService.unlock(lockKey);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new exceptions_1.MurLockException(`Failed to release lock for key ${lockKey}: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
66
94
|
//# sourceMappingURL=murlock.decorator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"murlock.decorator.js","sourceRoot":"","sources":["../../lib/decorators/murlock.decorator.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"murlock.decorator.js","sourceRoot":"","sources":["../../lib/decorators/murlock.decorator.ts"],"names":[],"mappings":"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"}
|
package/dist/murlock.module.js
CHANGED
|
@@ -12,6 +12,7 @@ const common_1 = require("@nestjs/common");
|
|
|
12
12
|
const murlock_service_1 = require("./murlock.service");
|
|
13
13
|
require("reflect-metadata");
|
|
14
14
|
const constants_1 = require("./constants");
|
|
15
|
+
const nestjs_cls_1 = require("nestjs-cls");
|
|
15
16
|
let MurLockModule = exports.MurLockModule = MurLockModule_1 = class MurLockModule {
|
|
16
17
|
static registerSync(options) {
|
|
17
18
|
return {
|
|
@@ -64,6 +65,7 @@ let MurLockModule = exports.MurLockModule = MurLockModule_1 = class MurLockModul
|
|
|
64
65
|
exports.MurLockModule = MurLockModule = MurLockModule_1 = __decorate([
|
|
65
66
|
(0, common_1.Global)(),
|
|
66
67
|
(0, common_1.Module)({
|
|
68
|
+
imports: [nestjs_cls_1.ClsModule],
|
|
67
69
|
providers: [murlock_service_1.MurLockService],
|
|
68
70
|
exports: [murlock_service_1.MurLockService],
|
|
69
71
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"murlock.module.js","sourceRoot":"","sources":["../lib/murlock.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAyE;AACzE,uDAAmD;AACnD,4BAA0B;AAE1B,2CAA2D;
|
|
1
|
+
{"version":3,"file":"murlock.module.js","sourceRoot":"","sources":["../lib/murlock.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAyE;AACzE,uDAAmD;AACnD,4BAA0B;AAE1B,2CAA2D;AAC3D,2CAAuC;AAQhC,IAAM,aAAa,6CAAnB,MAAM,aAAa;IACxB,MAAM,CAAC,YAAY,CAAC,OAA6B;QAC/C,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,iBAAiB;oBAC1B,QAAQ,EAAE,OAAO;iBAClB;gBACD,gCAAc;gBACd;oBACE,OAAO,EAAE,wCAA4B;oBACrC,UAAU,EAAE,CAAC,cAA8B,EAAE,EAAE;wBAC7C,OAAO,CAAC,cAAc,CAAC,wCAA4B,EAAE,cAAc,EAAE,gCAAc,CAAC,CAAC;wBACrF,OAAO,cAAc,CAAC;oBACxB,CAAC;oBACD,MAAM,EAAE,CAAC,gCAAc,CAAC;iBACzB;aACF;YACD,OAAO,EAAE,CAAC,gCAAc,CAAC;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,OAAkC;QACrD,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS,EAAE;gBACT,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC;gBACxC,gCAAc;gBACd;oBACE,OAAO,EAAE,wCAA4B;oBACrC,UAAU,EAAE,CAAC,cAA8B,EAAE,EAAE;wBAC7C,OAAO,CAAC,cAAc,CAAC,wCAA4B,EAAE,cAAc,EAAE,gCAAc,CAAC,CAAC;wBACrF,OAAO,cAAc,CAAC;oBACxB,CAAC;oBACD,MAAM,EAAE,CAAC,gCAAc,CAAC;iBACzB;aACF;YACD,OAAO,EAAE,CAAC,gCAAc,CAAC;SAC1B,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,0BAA0B,CAAC,OAAkC;QAC1E,OAAO;YACL,OAAO,EAAE,iBAAiB;YAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;SAC7B,CAAC;IACJ,CAAC;CACF,CAAA;wBAlDY,aAAa;IANzB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,sBAAS,CAAC;QACpB,SAAS,EAAE,CAAC,gCAAc,CAAC;QAC3B,OAAO,EAAE,CAAC,gCAAc,CAAC;KAC1B,CAAC;GACW,aAAa,CAkDzB"}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
|
2
2
|
import { MurLockModuleOptions } from './interfaces';
|
|
3
|
-
|
|
3
|
+
import { ClsService } from 'nestjs-cls';
|
|
4
|
+
export declare class MurLockService implements OnModuleInit, OnApplicationShutdown {
|
|
4
5
|
protected readonly options: MurLockModuleOptions;
|
|
6
|
+
private readonly clsService;
|
|
5
7
|
private readonly logger;
|
|
6
8
|
private redisClient;
|
|
7
|
-
|
|
9
|
+
private readonly lockScript;
|
|
10
|
+
private readonly unlockScript;
|
|
11
|
+
private runScriptAsync;
|
|
12
|
+
constructor(options: MurLockModuleOptions, clsService: ClsService);
|
|
8
13
|
onModuleInit(): void;
|
|
9
14
|
onApplicationShutdown(signal?: string): void;
|
|
15
|
+
private generateUuid;
|
|
16
|
+
private getClientId;
|
|
10
17
|
private sleep;
|
|
11
|
-
|
|
18
|
+
private log;
|
|
19
|
+
lock(lockKey: string, releaseTime: number, attemptsRemaining?: number): Promise<boolean>;
|
|
12
20
|
unlock(lockKey: string): Promise<void>;
|
|
13
21
|
}
|
package/dist/murlock.service.js
CHANGED
|
@@ -25,39 +25,74 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.MurLockService = void 0;
|
|
26
26
|
const common_1 = require("@nestjs/common");
|
|
27
27
|
const redis_1 = require("redis");
|
|
28
|
+
const util_1 = require("util");
|
|
29
|
+
const fs_1 = require("fs");
|
|
30
|
+
const path_1 = require("path");
|
|
31
|
+
const nestjs_cls_1 = require("nestjs-cls");
|
|
28
32
|
let MurLockService = exports.MurLockService = MurLockService_1 = class MurLockService {
|
|
29
|
-
constructor(options) {
|
|
33
|
+
constructor(options, clsService) {
|
|
30
34
|
this.options = options;
|
|
35
|
+
this.clsService = clsService;
|
|
31
36
|
this.logger = new common_1.Logger(MurLockService_1.name);
|
|
37
|
+
this.lockScript = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, './lua/lock.lua')).toString();
|
|
38
|
+
this.unlockScript = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, './lua/unlock.lua')).toString();
|
|
32
39
|
}
|
|
33
40
|
onModuleInit() {
|
|
34
41
|
this.redisClient = new redis_1.RedisClient(this.options.redisOptions);
|
|
35
|
-
this.
|
|
36
|
-
this.redisClient.on('error', (err) => this.
|
|
42
|
+
this.runScriptAsync = (0, util_1.promisify)(this.redisClient.eval).bind(this.redisClient);
|
|
43
|
+
this.redisClient.on('error', (err) => this.log('error', 'MurLock Redis Client Error', err));
|
|
37
44
|
}
|
|
38
45
|
onApplicationShutdown(signal) {
|
|
39
|
-
this.
|
|
46
|
+
this.log('log', 'MurLock Redis Disconnected.');
|
|
40
47
|
this.redisClient.quit();
|
|
41
48
|
}
|
|
49
|
+
generateUuid() {
|
|
50
|
+
let d = Date.now();
|
|
51
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
52
|
+
const r = (d + Math.random() * 16) % 16 | 0;
|
|
53
|
+
d = Math.floor(d / 16);
|
|
54
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
getClientId() {
|
|
58
|
+
let clientId = this.clsService.get('clientId');
|
|
59
|
+
if (!clientId) {
|
|
60
|
+
clientId = this.generateUuid();
|
|
61
|
+
this.clsService.set('clientId', clientId);
|
|
62
|
+
}
|
|
63
|
+
return clientId;
|
|
64
|
+
}
|
|
42
65
|
sleep(ms) {
|
|
43
66
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
67
|
}
|
|
45
|
-
|
|
68
|
+
log(level, message, context) {
|
|
69
|
+
const levels = ['debug', 'log', 'warn', 'error'];
|
|
70
|
+
if (levels.indexOf(level) >= levels.indexOf(this.options.logLevel)) {
|
|
71
|
+
this.logger[level](message, context);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
lock(lockKey, releaseTime, attemptsRemaining = this.options.maxAttempts) {
|
|
46
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
const clientId = this.getClientId();
|
|
77
|
+
this.log('debug', `MurLock Client ID is ${clientId}`);
|
|
47
78
|
const attemptLock = (attemptsRemaining) => __awaiter(this, void 0, void 0, function* () {
|
|
48
|
-
if (attemptsRemaining === 0)
|
|
49
|
-
|
|
79
|
+
if (attemptsRemaining === 0) {
|
|
80
|
+
throw new Error(`Failed to obtain lock for key ${lockKey} after ${this.options.maxAttempts} attempts.`);
|
|
81
|
+
}
|
|
50
82
|
try {
|
|
51
|
-
const isLockSuccessful = yield this.
|
|
52
|
-
if (isLockSuccessful) {
|
|
53
|
-
this.
|
|
83
|
+
const isLockSuccessful = yield this.runScriptAsync(this.lockScript, 1, lockKey, clientId, releaseTime);
|
|
84
|
+
if (isLockSuccessful === 1) {
|
|
85
|
+
this.log('log', `Successfully obtained lock for key ${lockKey}`);
|
|
54
86
|
return true;
|
|
55
87
|
}
|
|
56
|
-
|
|
57
|
-
|
|
88
|
+
else {
|
|
89
|
+
this.log('warn', `Failed to obtain lock for key ${lockKey}, retrying in ${this.options.wait} ms...`);
|
|
90
|
+
yield this.sleep(this.options.wait * (this.options.maxAttempts - attemptsRemaining + 1));
|
|
91
|
+
return attemptLock(attemptsRemaining - 1);
|
|
92
|
+
}
|
|
58
93
|
}
|
|
59
94
|
catch (error) {
|
|
60
|
-
this.
|
|
95
|
+
this.log('error', `Unexpected error when trying to obtain lock for key ${lockKey}:`, error);
|
|
61
96
|
return false;
|
|
62
97
|
}
|
|
63
98
|
});
|
|
@@ -66,14 +101,17 @@ let MurLockService = exports.MurLockService = MurLockService_1 = class MurLockSe
|
|
|
66
101
|
}
|
|
67
102
|
unlock(lockKey) {
|
|
68
103
|
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
|
|
70
|
-
this.
|
|
104
|
+
const clientId = this.getClientId();
|
|
105
|
+
const result = yield this.runScriptAsync(this.unlockScript, 1, lockKey, clientId);
|
|
106
|
+
if (result === 0) {
|
|
107
|
+
throw new Error(`Failed to release lock for key ${lockKey}`);
|
|
108
|
+
}
|
|
71
109
|
});
|
|
72
110
|
}
|
|
73
111
|
};
|
|
74
112
|
exports.MurLockService = MurLockService = MurLockService_1 = __decorate([
|
|
75
113
|
(0, common_1.Injectable)(),
|
|
76
114
|
__param(0, (0, common_1.Inject)('MURLOCK_OPTIONS')),
|
|
77
|
-
__metadata("design:paramtypes", [Object])
|
|
115
|
+
__metadata("design:paramtypes", [Object, nestjs_cls_1.ClsService])
|
|
78
116
|
], MurLockService);
|
|
79
117
|
//# sourceMappingURL=murlock.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"murlock.service.js","sourceRoot":"","sources":["../lib/murlock.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"murlock.service.js","sourceRoot":"","sources":["../lib/murlock.service.ts"],"names":[],"mappings":"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"}
|