atomic-queues 1.0.13
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 +686 -0
- package/dist/decorators/decorators.d.ts +67 -0
- package/dist/decorators/decorators.d.ts.map +1 -0
- package/dist/decorators/decorators.js +91 -0
- package/dist/decorators/decorators.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +18 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/domain/index.d.ts +5 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +21 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/interfaces.d.ts +614 -0
- package/dist/domain/interfaces.d.ts.map +1 -0
- package/dist/domain/interfaces.js +19 -0
- package/dist/domain/interfaces.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/module/atomic-queues.module.d.ts +97 -0
- package/dist/module/atomic-queues.module.d.ts.map +1 -0
- package/dist/module/atomic-queues.module.js +197 -0
- package/dist/module/atomic-queues.module.js.map +1 -0
- package/dist/module/index.d.ts +2 -0
- package/dist/module/index.d.ts.map +1 -0
- package/dist/module/index.js +18 -0
- package/dist/module/index.js.map +1 -0
- package/dist/services/constants.d.ts +10 -0
- package/dist/services/constants.d.ts.map +1 -0
- package/dist/services/constants.js +13 -0
- package/dist/services/constants.js.map +1 -0
- package/dist/services/cron-manager/cron-manager.service.d.ts +188 -0
- package/dist/services/cron-manager/cron-manager.service.d.ts.map +1 -0
- package/dist/services/cron-manager/cron-manager.service.js +534 -0
- package/dist/services/cron-manager/cron-manager.service.js.map +1 -0
- package/dist/services/cron-manager/index.d.ts +2 -0
- package/dist/services/cron-manager/index.d.ts.map +1 -0
- package/dist/services/cron-manager/index.js +18 -0
- package/dist/services/cron-manager/index.js.map +1 -0
- package/dist/services/index-manager/index-manager.service.d.ts +146 -0
- package/dist/services/index-manager/index-manager.service.d.ts.map +1 -0
- package/dist/services/index-manager/index-manager.service.js +337 -0
- package/dist/services/index-manager/index-manager.service.js.map +1 -0
- package/dist/services/index-manager/index.d.ts +2 -0
- package/dist/services/index-manager/index.d.ts.map +1 -0
- package/dist/services/index-manager/index.js +18 -0
- package/dist/services/index-manager/index.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +26 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/job-processor/index.d.ts +2 -0
- package/dist/services/job-processor/index.d.ts.map +1 -0
- package/dist/services/job-processor/index.js +18 -0
- package/dist/services/job-processor/index.js.map +1 -0
- package/dist/services/job-processor/job-processor.service.d.ts +156 -0
- package/dist/services/job-processor/job-processor.service.d.ts.map +1 -0
- package/dist/services/job-processor/job-processor.service.js +331 -0
- package/dist/services/job-processor/job-processor.service.js.map +1 -0
- package/dist/services/queue-manager/index.d.ts +2 -0
- package/dist/services/queue-manager/index.d.ts.map +1 -0
- package/dist/services/queue-manager/index.js +18 -0
- package/dist/services/queue-manager/index.js.map +1 -0
- package/dist/services/queue-manager/queue-manager.service.d.ts +128 -0
- package/dist/services/queue-manager/queue-manager.service.d.ts.map +1 -0
- package/dist/services/queue-manager/queue-manager.service.js +308 -0
- package/dist/services/queue-manager/queue-manager.service.js.map +1 -0
- package/dist/services/resource-lock/index.d.ts +2 -0
- package/dist/services/resource-lock/index.d.ts.map +1 -0
- package/dist/services/resource-lock/index.js +18 -0
- package/dist/services/resource-lock/index.js.map +1 -0
- package/dist/services/resource-lock/resource-lock.service.d.ts +124 -0
- package/dist/services/resource-lock/resource-lock.service.d.ts.map +1 -0
- package/dist/services/resource-lock/resource-lock.service.js +379 -0
- package/dist/services/resource-lock/resource-lock.service.js.map +1 -0
- package/dist/services/service-queue/index.d.ts +2 -0
- package/dist/services/service-queue/index.d.ts.map +1 -0
- package/dist/services/service-queue/index.js +18 -0
- package/dist/services/service-queue/index.js.map +1 -0
- package/dist/services/service-queue/service-queue.service.d.ts +232 -0
- package/dist/services/service-queue/service-queue.service.d.ts.map +1 -0
- package/dist/services/service-queue/service-queue.service.js +647 -0
- package/dist/services/service-queue/service-queue.service.js.map +1 -0
- package/dist/services/shutdown-state/index.d.ts +2 -0
- package/dist/services/shutdown-state/index.d.ts.map +1 -0
- package/dist/services/shutdown-state/index.js +18 -0
- package/dist/services/shutdown-state/index.js.map +1 -0
- package/dist/services/shutdown-state/shutdown-state.service.d.ts +69 -0
- package/dist/services/shutdown-state/shutdown-state.service.d.ts.map +1 -0
- package/dist/services/shutdown-state/shutdown-state.service.js +127 -0
- package/dist/services/shutdown-state/shutdown-state.service.js.map +1 -0
- package/dist/services/worker-manager/index.d.ts +2 -0
- package/dist/services/worker-manager/index.d.ts.map +1 -0
- package/dist/services/worker-manager/index.js +18 -0
- package/dist/services/worker-manager/index.js.map +1 -0
- package/dist/services/worker-manager/worker-manager.service.d.ts +163 -0
- package/dist/services/worker-manager/worker-manager.service.d.ts.map +1 -0
- package/dist/services/worker-manager/worker-manager.service.js +460 -0
- package/dist/services/worker-manager/worker-manager.service.js.map +1 -0
- package/dist/utils/helpers.d.ts +124 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +229 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var ResourceLockService_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.ResourceLockService = void 0;
|
|
20
|
+
const common_1 = require("@nestjs/common");
|
|
21
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
22
|
+
const constants_1 = require("../constants");
|
|
23
|
+
/**
|
|
24
|
+
* ResourceLockService
|
|
25
|
+
*
|
|
26
|
+
* Provides distributed resource locking using Redis.
|
|
27
|
+
* Implements patterns from both Whatsapi (context locking) and bl-blackjack-service.
|
|
28
|
+
*
|
|
29
|
+
* Key Features:
|
|
30
|
+
* - Atomic lock acquisition using Lua scripts
|
|
31
|
+
* - TTL-based lock expiration
|
|
32
|
+
* - Lock ownership verification
|
|
33
|
+
* - Pool-based resource allocation (get first available from pool)
|
|
34
|
+
* - Lock extension for long-running operations
|
|
35
|
+
*
|
|
36
|
+
* Use Cases:
|
|
37
|
+
* - Whatsapi: Lock WhatsApp contexts for message sending
|
|
38
|
+
* - Blackjack: Lock table resources for game operations
|
|
39
|
+
* - General: Any resource that needs exclusive access
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // Acquire a lock on a context
|
|
44
|
+
* const result = await lockService.acquireLock(
|
|
45
|
+
* 'context',
|
|
46
|
+
* 'context-123',
|
|
47
|
+
* 'user-456',
|
|
48
|
+
* 'user',
|
|
49
|
+
* 60, // 60 second TTL
|
|
50
|
+
* );
|
|
51
|
+
*
|
|
52
|
+
* if (result.acquired) {
|
|
53
|
+
* // Do work with the context
|
|
54
|
+
* await lockService.releaseLock('context', 'context-123');
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Get first available context from a pool
|
|
58
|
+
* const available = await lockService.getAvailableResource(
|
|
59
|
+
* 'context',
|
|
60
|
+
* ['ctx-1', 'ctx-2', 'ctx-3'],
|
|
61
|
+
* 'user-456',
|
|
62
|
+
* 'user',
|
|
63
|
+
* );
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
let ResourceLockService = ResourceLockService_1 = class ResourceLockService {
|
|
67
|
+
constructor(redis, config) {
|
|
68
|
+
this.redis = redis;
|
|
69
|
+
this.config = config;
|
|
70
|
+
this.logger = new common_1.Logger(ResourceLockService_1.name);
|
|
71
|
+
// Lua script for atomic lock acquisition
|
|
72
|
+
this.ACQUIRE_LOCK_SCRIPT = `
|
|
73
|
+
local lockKey = KEYS[1]
|
|
74
|
+
local lockValue = ARGV[1]
|
|
75
|
+
local ttl = tonumber(ARGV[2])
|
|
76
|
+
|
|
77
|
+
local current = redis.call("GET", lockKey)
|
|
78
|
+
|
|
79
|
+
if not current then
|
|
80
|
+
redis.call("SET", lockKey, lockValue, "EX", ttl)
|
|
81
|
+
return 1 -- Lock acquired
|
|
82
|
+
else
|
|
83
|
+
return 0 -- Lock already held
|
|
84
|
+
end
|
|
85
|
+
`;
|
|
86
|
+
// Lua script for atomic lock release with ownership check
|
|
87
|
+
this.RELEASE_LOCK_SCRIPT = `
|
|
88
|
+
local lockKey = KEYS[1]
|
|
89
|
+
local expectedOwner = ARGV[1]
|
|
90
|
+
|
|
91
|
+
local current = redis.call("GET", lockKey)
|
|
92
|
+
|
|
93
|
+
if current then
|
|
94
|
+
local lockData = cjson.decode(current)
|
|
95
|
+
if lockData.ownerId == expectedOwner then
|
|
96
|
+
redis.call("DEL", lockKey)
|
|
97
|
+
return 1 -- Lock released
|
|
98
|
+
else
|
|
99
|
+
return 0 -- Not the owner
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
return 1 -- Lock didn't exist
|
|
104
|
+
`;
|
|
105
|
+
// Lua script for extending lock TTL
|
|
106
|
+
this.EXTEND_LOCK_SCRIPT = `
|
|
107
|
+
local lockKey = KEYS[1]
|
|
108
|
+
local newTtl = tonumber(ARGV[1])
|
|
109
|
+
|
|
110
|
+
local exists = redis.call("EXISTS", lockKey)
|
|
111
|
+
|
|
112
|
+
if exists == 1 then
|
|
113
|
+
redis.call("EXPIRE", lockKey, newTtl)
|
|
114
|
+
return 1 -- TTL extended
|
|
115
|
+
else
|
|
116
|
+
return 0 -- Lock doesn't exist
|
|
117
|
+
end
|
|
118
|
+
`;
|
|
119
|
+
this.keyPrefix = config.keyPrefix || 'aq';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Acquire a lock on a resource.
|
|
123
|
+
*
|
|
124
|
+
* Uses Lua script for atomic check-and-set operation.
|
|
125
|
+
* The lock includes ownership information for later verification.
|
|
126
|
+
*/
|
|
127
|
+
async acquireLock(resourceType, resourceId, ownerId, ownerType, ttlSeconds = 60, metadata) {
|
|
128
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
129
|
+
const now = new Date();
|
|
130
|
+
const lockData = {
|
|
131
|
+
resourceId,
|
|
132
|
+
resourceType,
|
|
133
|
+
ownerId,
|
|
134
|
+
ownerType,
|
|
135
|
+
acquiredAt: now,
|
|
136
|
+
expiresAt: new Date(now.getTime() + ttlSeconds * 1000),
|
|
137
|
+
metadata,
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
const result = await this.redis.eval(this.ACQUIRE_LOCK_SCRIPT, 1, lockKey, JSON.stringify(lockData), ttlSeconds.toString());
|
|
141
|
+
if (result === 1) {
|
|
142
|
+
this.logger.debug(`Lock acquired: ${resourceType}/${resourceId} by ${ownerType}/${ownerId}`);
|
|
143
|
+
return { acquired: true, lock: lockData };
|
|
144
|
+
}
|
|
145
|
+
this.logger.debug(`Lock not acquired (already held): ${resourceType}/${resourceId}`);
|
|
146
|
+
return {
|
|
147
|
+
acquired: false,
|
|
148
|
+
reason: 'Resource is already locked',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
this.logger.error(`Error acquiring lock for ${resourceType}/${resourceId}:`, error);
|
|
153
|
+
return {
|
|
154
|
+
acquired: false,
|
|
155
|
+
reason: `Error: ${error.message}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Release a lock on a resource.
|
|
161
|
+
*/
|
|
162
|
+
async releaseLock(resourceType, resourceId) {
|
|
163
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
164
|
+
try {
|
|
165
|
+
await this.redis.del(lockKey);
|
|
166
|
+
this.logger.debug(`Lock released: ${resourceType}/${resourceId}`);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
this.logger.error(`Error releasing lock for ${resourceType}/${resourceId}:`, error);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Release a lock with ownership verification.
|
|
176
|
+
* Only releases if the current owner matches the expected owner.
|
|
177
|
+
*/
|
|
178
|
+
async releaseOwnedLock(resourceType, resourceId, ownerId) {
|
|
179
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
180
|
+
try {
|
|
181
|
+
const result = await this.redis.eval(this.RELEASE_LOCK_SCRIPT, 1, lockKey, ownerId);
|
|
182
|
+
const released = result === 1;
|
|
183
|
+
if (released) {
|
|
184
|
+
this.logger.debug(`Lock released by owner: ${resourceType}/${resourceId}`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.logger.warn(`Lock release failed (not owner): ${resourceType}/${resourceId}`);
|
|
188
|
+
}
|
|
189
|
+
return released;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger.error(`Error releasing owned lock for ${resourceType}/${resourceId}:`, error);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if a resource is locked.
|
|
198
|
+
*/
|
|
199
|
+
async isLocked(resourceType, resourceId) {
|
|
200
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
201
|
+
const exists = await this.redis.exists(lockKey);
|
|
202
|
+
return exists === 1;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get lock info for a resource.
|
|
206
|
+
*/
|
|
207
|
+
async getLockInfo(resourceType, resourceId) {
|
|
208
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
209
|
+
const data = await this.redis.get(lockKey);
|
|
210
|
+
if (!data)
|
|
211
|
+
return null;
|
|
212
|
+
try {
|
|
213
|
+
const lock = JSON.parse(data);
|
|
214
|
+
// Convert date strings back to Date objects
|
|
215
|
+
lock.acquiredAt = new Date(lock.acquiredAt);
|
|
216
|
+
lock.expiresAt = new Date(lock.expiresAt);
|
|
217
|
+
return lock;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
this.logger.error(`Error parsing lock data for ${lockKey}:`, error);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get all locked resources of a type for an owner.
|
|
226
|
+
*/
|
|
227
|
+
async getOwnerLocks(ownerType, ownerId) {
|
|
228
|
+
// Scan for all locks and filter by owner
|
|
229
|
+
const pattern = `${this.keyPrefix}:lock:*`;
|
|
230
|
+
const keys = await this.scanKeys(pattern);
|
|
231
|
+
const locks = [];
|
|
232
|
+
for (const key of keys) {
|
|
233
|
+
const data = await this.redis.get(key);
|
|
234
|
+
if (data) {
|
|
235
|
+
try {
|
|
236
|
+
const lock = JSON.parse(data);
|
|
237
|
+
if (lock.ownerType === ownerType && lock.ownerId === ownerId) {
|
|
238
|
+
lock.acquiredAt = new Date(lock.acquiredAt);
|
|
239
|
+
lock.expiresAt = new Date(lock.expiresAt);
|
|
240
|
+
locks.push(lock);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Skip invalid entries
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return locks;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get an available (unlocked) resource from a pool of candidates.
|
|
252
|
+
* Shuffles the pool for fair distribution.
|
|
253
|
+
* Atomically locks and returns the first available resource.
|
|
254
|
+
*
|
|
255
|
+
* This is the pattern used by Whatsapi to find an available WhatsApp context.
|
|
256
|
+
*/
|
|
257
|
+
async getAvailableResource(resourceType, candidateIds, ownerId, ownerType, ttlSeconds = 60) {
|
|
258
|
+
// Shuffle candidates for fair distribution
|
|
259
|
+
const shuffled = this.shuffleArray([...candidateIds]);
|
|
260
|
+
for (const resourceId of shuffled) {
|
|
261
|
+
const result = await this.acquireLock(resourceType, resourceId, ownerId, ownerType, ttlSeconds);
|
|
262
|
+
if (result.acquired) {
|
|
263
|
+
this.logger.debug(`Found available resource: ${resourceType}/${resourceId}`);
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.logger.debug(`No available resource found in pool for ${ownerType}/${ownerId}`);
|
|
268
|
+
return {
|
|
269
|
+
acquired: false,
|
|
270
|
+
reason: 'No available resources in pool',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Extend the TTL of an existing lock.
|
|
275
|
+
*/
|
|
276
|
+
async extendLock(resourceType, resourceId, ttlSeconds) {
|
|
277
|
+
const lockKey = this.getLockKey(resourceType, resourceId);
|
|
278
|
+
try {
|
|
279
|
+
const result = await this.redis.eval(this.EXTEND_LOCK_SCRIPT, 1, lockKey, ttlSeconds.toString());
|
|
280
|
+
const extended = result === 1;
|
|
281
|
+
if (extended) {
|
|
282
|
+
this.logger.debug(`Lock TTL extended: ${resourceType}/${resourceId} to ${ttlSeconds}s`);
|
|
283
|
+
}
|
|
284
|
+
return extended;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
this.logger.error(`Error extending lock for ${resourceType}/${resourceId}:`, error);
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get all locks for a resource type.
|
|
293
|
+
*/
|
|
294
|
+
async getResourceTypeLocks(resourceType) {
|
|
295
|
+
const pattern = `${this.keyPrefix}:lock:${resourceType}:*`;
|
|
296
|
+
const keys = await this.scanKeys(pattern);
|
|
297
|
+
const locked = [];
|
|
298
|
+
for (const key of keys) {
|
|
299
|
+
const data = await this.redis.get(key);
|
|
300
|
+
if (data) {
|
|
301
|
+
try {
|
|
302
|
+
const lock = JSON.parse(data);
|
|
303
|
+
lock.acquiredAt = new Date(lock.acquiredAt);
|
|
304
|
+
lock.expiresAt = new Date(lock.expiresAt);
|
|
305
|
+
locked.push(lock);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Skip invalid entries
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
locked,
|
|
314
|
+
free: [], // This would need external resource registry to determine
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Cross-verify that a lock is still held by the expected owner.
|
|
319
|
+
* Useful for operations that need to verify lock ownership mid-operation.
|
|
320
|
+
*/
|
|
321
|
+
async verifyLockOwnership(resourceType, resourceId, ownerId) {
|
|
322
|
+
const lock = await this.getLockInfo(resourceType, resourceId);
|
|
323
|
+
return lock !== null && lock.ownerId === ownerId;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Clear all locks for an owner (useful for cleanup).
|
|
327
|
+
*/
|
|
328
|
+
async clearOwnerLocks(ownerType, ownerId) {
|
|
329
|
+
const locks = await this.getOwnerLocks(ownerType, ownerId);
|
|
330
|
+
let cleared = 0;
|
|
331
|
+
for (const lock of locks) {
|
|
332
|
+
const released = await this.releaseLock(lock.resourceType, lock.resourceId);
|
|
333
|
+
if (released)
|
|
334
|
+
cleared++;
|
|
335
|
+
}
|
|
336
|
+
this.logger.debug(`Cleared ${cleared} locks for ${ownerType}/${ownerId}`);
|
|
337
|
+
return cleared;
|
|
338
|
+
}
|
|
339
|
+
// =========================================================================
|
|
340
|
+
// PRIVATE METHODS
|
|
341
|
+
// =========================================================================
|
|
342
|
+
/**
|
|
343
|
+
* Get the Redis key for a lock.
|
|
344
|
+
*/
|
|
345
|
+
getLockKey(resourceType, resourceId) {
|
|
346
|
+
return `${this.keyPrefix}:lock:${resourceType}:${resourceId}`;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Scan Redis keys matching a pattern.
|
|
350
|
+
*/
|
|
351
|
+
async scanKeys(pattern) {
|
|
352
|
+
let cursor = '0';
|
|
353
|
+
const keys = [];
|
|
354
|
+
do {
|
|
355
|
+
const [nextCursor, scanKeys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
356
|
+
cursor = nextCursor;
|
|
357
|
+
keys.push(...scanKeys);
|
|
358
|
+
} while (cursor !== '0');
|
|
359
|
+
return keys;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Fisher-Yates shuffle algorithm.
|
|
363
|
+
*/
|
|
364
|
+
shuffleArray(array) {
|
|
365
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
366
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
367
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
368
|
+
}
|
|
369
|
+
return array;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
exports.ResourceLockService = ResourceLockService;
|
|
373
|
+
exports.ResourceLockService = ResourceLockService = ResourceLockService_1 = __decorate([
|
|
374
|
+
(0, common_1.Injectable)(),
|
|
375
|
+
__param(0, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_REDIS)),
|
|
376
|
+
__param(1, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_CONFIG)),
|
|
377
|
+
__metadata("design:paramtypes", [ioredis_1.default, Object])
|
|
378
|
+
], ResourceLockService);
|
|
379
|
+
//# sourceMappingURL=resource-lock.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-lock.service.js","sourceRoot":"","sources":["../../../src/services/resource-lock/resource-lock.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,sDAA4B;AAO5B,4CAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEI,IAAM,mBAAmB,2BAAzB,MAAM,mBAAmB;IAuD9B,YAC+B,KAA6B,EAE1D,MAAkD;QAFJ,UAAK,GAAL,KAAK,CAAO;QAEzC,WAAM,GAAN,MAAM,CAA2B;QAzDnC,WAAM,GAAG,IAAI,eAAM,CAAC,qBAAmB,CAAC,IAAI,CAAC,CAAC;QAG/D,yCAAyC;QACxB,wBAAmB,GAAG;;;;;;;;;;;;;GAatC,CAAC;QAEF,0DAA0D;QACzC,wBAAmB,GAAG;;;;;;;;;;;;;;;;;GAiBtC,CAAC;QAEF,oCAAoC;QACnB,uBAAkB,GAAG;;;;;;;;;;;;GAYrC,CAAC;QAOA,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CACf,YAAoB,EACpB,UAAkB,EAClB,OAAe,EACf,SAAiB,EACjB,UAAU,GAAG,EAAE,EACf,QAAkC;QAElC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAEvB,MAAM,QAAQ,GAAkB;YAC9B,UAAU;YACV,YAAY;YACZ,OAAO;YACP,SAAS;YACT,UAAU,EAAE,GAAG;YACf,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC;YACtD,QAAQ;SACT,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAClC,IAAI,CAAC,mBAAmB,EACxB,CAAC,EACD,OAAO,EACP,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EACxB,UAAU,CAAC,QAAQ,EAAE,CACtB,CAAC;YAEF,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,kBAAkB,YAAY,IAAI,UAAU,OAAO,SAAS,IAAI,OAAO,EAAE,CAC1E,CAAC;gBACF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC5C,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,qCAAqC,YAAY,IAAI,UAAU,EAAE,CAClE,CAAC;YACF,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,4BAA4B;aACrC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,4BAA4B,YAAY,IAAI,UAAU,GAAG,EACzD,KAAK,CACN,CAAC;YACF,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE;aAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,YAAoB,EACpB,UAAkB;QAElB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAE1D,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,YAAY,IAAI,UAAU,EAAE,CAAC,CAAC;YAClE,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,4BAA4B,YAAY,IAAI,UAAU,GAAG,EACzD,KAAK,CACN,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB,CACpB,YAAoB,EACpB,UAAkB,EAClB,OAAe;QAEf,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAE1D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAClC,IAAI,CAAC,mBAAmB,EACxB,CAAC,EACD,OAAO,EACP,OAAO,CACR,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,CAAC;YAC9B,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,2BAA2B,YAAY,IAAI,UAAU,EAAE,CACxD,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,oCAAoC,YAAY,IAAI,UAAU,EAAE,CACjE,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,kCAAkC,YAAY,IAAI,UAAU,GAAG,EAC/D,KAAK,CACN,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,YAAoB,EAAE,UAAkB;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,MAAM,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,YAAoB,EACpB,UAAkB;QAElB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE3C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;YAC/C,4CAA4C;YAC5C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,SAAiB,EACjB,OAAe;QAEf,yCAAyC;QACzC,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,SAAS,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAoB,EAAE,CAAC;QAElC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;oBAC/C,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;wBAC7D,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;wBAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC1C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,uBAAuB;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,oBAAoB,CACxB,YAAoB,EACpB,YAAsB,EACtB,OAAe,EACf,SAAiB,EACjB,UAAU,GAAG,EAAE;QAEf,2CAA2C;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;QAEtD,KAAK,MAAM,UAAU,IAAI,QAAQ,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CACnC,YAAY,EACZ,UAAU,EACV,OAAO,EACP,SAAS,EACT,UAAU,CACX,CAAC;YAEF,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,6BAA6B,YAAY,IAAI,UAAU,EAAE,CAC1D,CAAC;gBACF,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,2CAA2C,SAAS,IAAI,OAAO,EAAE,CAClE,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,gCAAgC;SACzC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CACd,YAAoB,EACpB,UAAkB,EAClB,UAAkB;QAElB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAE1D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAClC,IAAI,CAAC,kBAAkB,EACvB,CAAC,EACD,OAAO,EACP,UAAU,CAAC,QAAQ,EAAE,CACtB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,CAAC;YAC9B,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,sBAAsB,YAAY,IAAI,UAAU,OAAO,UAAU,GAAG,CACrE,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,4BAA4B,YAAY,IAAI,UAAU,GAAG,EACzD,KAAK,CACN,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,oBAAoB,CAAC,YAAoB;QAI7C,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,SAAS,YAAY,IAAI,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAoB,EAAE,CAAC;QAEnC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;oBAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;oBAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpB,CAAC;gBAAC,MAAM,CAAC;oBACP,uBAAuB;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM;YACN,IAAI,EAAE,EAAE,EAAE,0DAA0D;SACrE,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CACvB,YAAoB,EACpB,UAAkB,EAClB,OAAe;QAEf,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9D,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,OAAe;QACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC3D,IAAI,OAAO,GAAG,CAAC,CAAC;QAEhB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CACrC,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAChB,CAAC;YACF,IAAI,QAAQ;gBAAE,OAAO,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,WAAW,OAAO,cAAc,SAAS,IAAI,OAAO,EAAE,CACvD,CAAC;QACF,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,4EAA4E;IAC5E,kBAAkB;IAClB,4EAA4E;IAE5E;;OAEG;IACK,UAAU,CAAC,YAAoB,EAAE,UAAkB;QACzD,OAAO,GAAG,IAAI,CAAC,SAAS,SAAS,YAAY,IAAI,UAAU,EAAE,CAAC;IAChE,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,QAAQ,CAAC,OAAe;QACpC,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,GAAG,CAAC;YACF,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAClD,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,GAAG,CACJ,CAAC;YACF,MAAM,GAAG,UAAU,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;QACzB,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;QAEzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,YAAY,CAAI,KAAU;QAChC,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAA;AAnbY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;IAyDR,WAAA,IAAA,eAAM,EAAC,+BAAmB,CAAC,CAAA;IAC3B,WAAA,IAAA,eAAM,EAAC,gCAAoB,CAAC,CAAA;qCADwB,iBAAK;GAxDjD,mBAAmB,CAmb/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/service-queue/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./service-queue.service"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/service-queue/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { OnModuleInit, OnApplicationShutdown } from '@nestjs/common';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
import { IAtomicQueuesModuleConfig } from '../../domain';
|
|
4
|
+
/**
|
|
5
|
+
* Service-level job names for global atomic operations.
|
|
6
|
+
* These operations MUST be processed by exactly ONE worker across the entire distributed system.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum ServiceQueueJobNames {
|
|
9
|
+
/** Get the count of all workers across all nodes */
|
|
10
|
+
GET_GLOBAL_WORKER_COUNT = "get-global-worker-count",
|
|
11
|
+
/** Get workers for a specific entity across all nodes */
|
|
12
|
+
GET_ENTITY_WORKERS = "get-entity-workers",
|
|
13
|
+
/** Verify ownership of a resource */
|
|
14
|
+
VERIFY_OWNERSHIP = "verify-ownership",
|
|
15
|
+
/** Acquire global lock */
|
|
16
|
+
ACQUIRE_GLOBAL_LOCK = "acquire-global-lock",
|
|
17
|
+
/** Release global lock */
|
|
18
|
+
RELEASE_GLOBAL_LOCK = "release-global-lock",
|
|
19
|
+
/** Run scaling cycle for CronManager - triggers worker spawn/terminate decisions */
|
|
20
|
+
RUN_SCALING_CYCLE = "run-scaling-cycle",
|
|
21
|
+
/** Spawn a worker for a specific entity - used when opening a table/entity */
|
|
22
|
+
SPAWN_ENTITY_WORKER = "spawn-entity-worker",
|
|
23
|
+
/** Custom service operation */
|
|
24
|
+
CUSTOM = "custom"
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Job data for service queue operations
|
|
28
|
+
*/
|
|
29
|
+
export interface IServiceQueueJobData<T = unknown> {
|
|
30
|
+
uuid: string;
|
|
31
|
+
jobName: ServiceQueueJobNames;
|
|
32
|
+
payload: T;
|
|
33
|
+
responseChannel?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* ServiceQueueManager
|
|
37
|
+
*
|
|
38
|
+
* Manages the global service-level queue for operations that MUST be atomic
|
|
39
|
+
* across the entire distributed system. Unlike per-entity queues that can have
|
|
40
|
+
* one worker per entity, the service queue has EXACTLY ONE worker globally.
|
|
41
|
+
*
|
|
42
|
+
* Use cases:
|
|
43
|
+
* - Querying global worker counts (can't race with worker creation/deletion)
|
|
44
|
+
* - Ownership verification for resources
|
|
45
|
+
* - Global state mutations
|
|
46
|
+
* - Cross-node coordination
|
|
47
|
+
*
|
|
48
|
+
* Architecture:
|
|
49
|
+
* - Uses a distributed lock to ensure only ONE service worker exists globally
|
|
50
|
+
* - The worker can run on ANY node
|
|
51
|
+
* - If the worker dies, another node will acquire the lock and spawn it
|
|
52
|
+
* - All operations go through the single queue for serialization
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Execute a global atomic operation
|
|
57
|
+
* const workerCount = await serviceQueue.executeServiceOperation(
|
|
58
|
+
* ServiceQueueJobNames.GET_GLOBAL_WORKER_COUNT,
|
|
59
|
+
* { entityType: 'table' },
|
|
60
|
+
* );
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare class ServiceQueueManager implements OnModuleInit, OnApplicationShutdown {
|
|
64
|
+
private readonly redis;
|
|
65
|
+
private readonly config;
|
|
66
|
+
private readonly logger;
|
|
67
|
+
private readonly keyPrefix;
|
|
68
|
+
private readonly serviceQueueName;
|
|
69
|
+
private readonly serviceWorkerName;
|
|
70
|
+
private readonly lockKey;
|
|
71
|
+
private readonly lockTTL;
|
|
72
|
+
private readonly lockRenewalInterval;
|
|
73
|
+
private serviceQueue;
|
|
74
|
+
private serviceWorker;
|
|
75
|
+
private lockRenewalTimer;
|
|
76
|
+
private subscriberClient;
|
|
77
|
+
private hasLock;
|
|
78
|
+
private readonly nodeId;
|
|
79
|
+
private readonly pendingOperations;
|
|
80
|
+
private readonly customProcessors;
|
|
81
|
+
private scalingCycleHandler;
|
|
82
|
+
constructor(redis: Redis, config: IAtomicQueuesModuleConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Initialize on module start.
|
|
85
|
+
* Attempts to acquire the global service worker lock.
|
|
86
|
+
*/
|
|
87
|
+
onModuleInit(): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Cleanup on shutdown.
|
|
90
|
+
*/
|
|
91
|
+
onApplicationShutdown(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Execute a service-level operation atomically.
|
|
94
|
+
* This queues the operation to the service queue and waits for the result.
|
|
95
|
+
*
|
|
96
|
+
* @param jobName The type of operation to execute
|
|
97
|
+
* @param payload The operation payload
|
|
98
|
+
* @param timeoutMs Timeout in milliseconds (default: 30000)
|
|
99
|
+
* @returns The operation result
|
|
100
|
+
*/
|
|
101
|
+
executeServiceOperation<T, R>(jobName: ServiceQueueJobNames, payload: T, timeoutMs?: number): Promise<R>;
|
|
102
|
+
/**
|
|
103
|
+
* Queue a service operation without waiting for result (fire-and-forget).
|
|
104
|
+
*/
|
|
105
|
+
queueServiceOperation<T>(jobName: ServiceQueueJobNames, payload: T): Promise<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Register a custom processor for service-level operations.
|
|
108
|
+
* This allows the consuming application to add custom atomic operations.
|
|
109
|
+
*/
|
|
110
|
+
registerCustomProcessor(name: string, processor: (payload: unknown) => Promise<unknown>): void;
|
|
111
|
+
/**
|
|
112
|
+
* Check if this node is the service worker owner.
|
|
113
|
+
*/
|
|
114
|
+
isServiceWorkerOwner(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Register the scaling cycle handler (called by CronManager).
|
|
117
|
+
* This allows CronManager to register its internal scaling logic to be
|
|
118
|
+
* executed atomically by the service worker.
|
|
119
|
+
*/
|
|
120
|
+
registerScalingCycleHandler(handler: (entityType: string) => Promise<unknown>): void;
|
|
121
|
+
/**
|
|
122
|
+
* Spawn worker handler type - directly spawns a worker for a specific entity.
|
|
123
|
+
*/
|
|
124
|
+
private spawnWorkerHandler?;
|
|
125
|
+
/**
|
|
126
|
+
* Register the spawn worker handler (called by CronManager or TableWorkerScalingService).
|
|
127
|
+
* This allows directly spawning a worker for a specific entity without waiting
|
|
128
|
+
* for the next scaling cycle.
|
|
129
|
+
*/
|
|
130
|
+
registerSpawnWorkerHandler(handler: (entityType: string, entityId: string) => Promise<void>): void;
|
|
131
|
+
/**
|
|
132
|
+
* Trigger a scaling cycle for an entity type.
|
|
133
|
+
* This queues the job to the service queue - only the service worker will execute it.
|
|
134
|
+
*/
|
|
135
|
+
triggerScalingCycle(entityType: string): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Request spawning a worker for a specific entity.
|
|
138
|
+
* This is used when an entity (e.g., table) is opened and needs a worker immediately,
|
|
139
|
+
* without waiting for the next scaling cycle.
|
|
140
|
+
*
|
|
141
|
+
* The job is processed by the service worker to ensure atomic operation.
|
|
142
|
+
*
|
|
143
|
+
* @param entityType The type of entity (e.g., 'table')
|
|
144
|
+
* @param entityId The ID of the entity (e.g., tableId)
|
|
145
|
+
*/
|
|
146
|
+
requestSpawnEntityWorker(entityType: string, entityId: string): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Get the service queue name.
|
|
149
|
+
*/
|
|
150
|
+
getQueueName(): string;
|
|
151
|
+
/**
|
|
152
|
+
* Get the service worker name.
|
|
153
|
+
*/
|
|
154
|
+
getWorkerName(): string;
|
|
155
|
+
/**
|
|
156
|
+
* Get pending job count in the service queue.
|
|
157
|
+
*/
|
|
158
|
+
getQueueDepth(): Promise<number>;
|
|
159
|
+
/**
|
|
160
|
+
* Try to acquire the global service worker lock.
|
|
161
|
+
*/
|
|
162
|
+
private tryAcquireServiceWorkerLock;
|
|
163
|
+
/**
|
|
164
|
+
* Release the service worker lock.
|
|
165
|
+
*/
|
|
166
|
+
private releaseLock;
|
|
167
|
+
/**
|
|
168
|
+
* Start periodic lock renewal.
|
|
169
|
+
*/
|
|
170
|
+
private startLockRenewal;
|
|
171
|
+
/**
|
|
172
|
+
* Start periodic lock acquisition attempts.
|
|
173
|
+
* Retries more frequently initially, then backs off to lockTTL interval.
|
|
174
|
+
*/
|
|
175
|
+
private startLockAcquisitionLoop;
|
|
176
|
+
/**
|
|
177
|
+
* Start the service worker.
|
|
178
|
+
*/
|
|
179
|
+
private startServiceWorker;
|
|
180
|
+
/**
|
|
181
|
+
* Stop the service worker.
|
|
182
|
+
*/
|
|
183
|
+
private stopServiceWorker;
|
|
184
|
+
/**
|
|
185
|
+
* Process a service queue job.
|
|
186
|
+
*/
|
|
187
|
+
private processServiceJob;
|
|
188
|
+
/**
|
|
189
|
+
* Wait for a response on a channel.
|
|
190
|
+
*/
|
|
191
|
+
private waitForResponse;
|
|
192
|
+
/**
|
|
193
|
+
* Get global worker count across all nodes.
|
|
194
|
+
*/
|
|
195
|
+
private handleGetGlobalWorkerCount;
|
|
196
|
+
/**
|
|
197
|
+
* Get workers for a specific entity.
|
|
198
|
+
* Uses the worker heartbeat TTL keys as the single source of truth.
|
|
199
|
+
*/
|
|
200
|
+
private handleGetEntityWorkers;
|
|
201
|
+
/**
|
|
202
|
+
* Verify ownership of a resource.
|
|
203
|
+
*/
|
|
204
|
+
private handleVerifyOwnership;
|
|
205
|
+
/**
|
|
206
|
+
* Acquire a global lock atomically.
|
|
207
|
+
*/
|
|
208
|
+
private handleAcquireGlobalLock;
|
|
209
|
+
/**
|
|
210
|
+
* Release a global lock atomically.
|
|
211
|
+
*/
|
|
212
|
+
private handleReleaseGlobalLock;
|
|
213
|
+
/**
|
|
214
|
+
* Handle custom operation by delegating to registered processor.
|
|
215
|
+
*/
|
|
216
|
+
private handleCustomOperation;
|
|
217
|
+
/**
|
|
218
|
+
* Handle scaling cycle request by delegating to registered CronManager handler.
|
|
219
|
+
*/
|
|
220
|
+
private handleRunScalingCycle;
|
|
221
|
+
/**
|
|
222
|
+
* Handle spawn entity worker request.
|
|
223
|
+
* This directly spawns a worker for the specific entity, bypassing the scaling cycle.
|
|
224
|
+
* Used when opening a table/entity that needs a worker immediately.
|
|
225
|
+
*/
|
|
226
|
+
private handleSpawnEntityWorker;
|
|
227
|
+
/**
|
|
228
|
+
* Scan Redis keys matching a pattern.
|
|
229
|
+
*/
|
|
230
|
+
private scanKeys;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=service-queue.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-queue.service.d.ts","sourceRoot":"","sources":["../../../src/services/service-queue/service-queue.service.ts"],"names":[],"mappings":"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"}
|