@prsm/queue 3.0.2 → 3.0.6
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/package.json +3 -2
- package/src/queue.js +17 -52
- package/types/queue.d.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prsm/queue",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "Redis-backed distributed task queue with grouped concurrency, retries, and rate limiting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "vitest --reporter=verbose --run",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
|
-
"prepublishOnly": "npx tsc
|
|
20
|
+
"prepublishOnly": "npx tsc -p tsconfig.json"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"queue",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@prsm/lock": "^1.0.0",
|
|
35
36
|
"@prsm/ms": "^1.0.1",
|
|
36
37
|
"redis": "^5.1.1"
|
|
37
38
|
},
|
package/src/queue.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createClient } from "redis"
|
|
|
2
2
|
import { EventEmitter } from "events"
|
|
3
3
|
import { randomUUID } from "crypto"
|
|
4
4
|
import ms from "@prsm/ms"
|
|
5
|
+
import { semaphore as createSemaphore } from "@prsm/lock"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {Object} QueueOptions
|
|
@@ -31,36 +32,6 @@ import ms from "@prsm/ms"
|
|
|
31
32
|
* @returns {Promise<any>|any}
|
|
32
33
|
*/
|
|
33
34
|
|
|
34
|
-
const ACQUIRE_SCRIPT = `
|
|
35
|
-
local key = KEYS[1]
|
|
36
|
-
local max = tonumber(ARGV[1])
|
|
37
|
-
local id = ARGV[2]
|
|
38
|
-
local ttl = tonumber(ARGV[3])
|
|
39
|
-
local time = redis.call('TIME')
|
|
40
|
-
local now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)
|
|
41
|
-
redis.call('ZREMRANGEBYSCORE', key, '-inf', now - ttl)
|
|
42
|
-
if redis.call('ZCARD', key) < max then
|
|
43
|
-
redis.call('ZADD', key, now, id)
|
|
44
|
-
return 1
|
|
45
|
-
end
|
|
46
|
-
return 0
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
const RELEASE_SCRIPT = `
|
|
50
|
-
redis.call('ZREM', KEYS[1], ARGV[1])
|
|
51
|
-
return 1
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
const RENEW_SCRIPT = `
|
|
55
|
-
local time = redis.call('TIME')
|
|
56
|
-
local now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)
|
|
57
|
-
if redis.call('ZSCORE', KEYS[1], ARGV[1]) then
|
|
58
|
-
redis.call('ZADD', KEYS[1], now, ARGV[1])
|
|
59
|
-
return 1
|
|
60
|
-
end
|
|
61
|
-
return 0
|
|
62
|
-
`
|
|
63
|
-
|
|
64
35
|
const LEASE_TTL = 60000
|
|
65
36
|
const HEARTBEAT_INTERVAL = 15000
|
|
66
37
|
const CLOSE_TIMEOUT = 5000
|
|
@@ -131,9 +102,16 @@ export default class Queue extends EventEmitter {
|
|
|
131
102
|
|
|
132
103
|
this._redis = createClient(this._options.redisOptions)
|
|
133
104
|
this._redis.on("error", () => {})
|
|
105
|
+
this._semaphore = this._options.globalConcurrency > 0
|
|
106
|
+
? createSemaphore({
|
|
107
|
+
max: this._options.globalConcurrency,
|
|
108
|
+
ttl: LEASE_TTL,
|
|
109
|
+
redis: this._options.redisOptions,
|
|
110
|
+
prefix: "",
|
|
111
|
+
})
|
|
112
|
+
: null
|
|
134
113
|
this._subClient = null
|
|
135
114
|
this._groupNotifyClient = null
|
|
136
|
-
this._pendingWaits = new Map()
|
|
137
115
|
this._readyPromise = this._initialize()
|
|
138
116
|
}
|
|
139
117
|
|
|
@@ -257,7 +235,6 @@ export default class Queue extends EventEmitter {
|
|
|
257
235
|
if (timer) clearTimeout(timer)
|
|
258
236
|
this.off("complete", onComplete)
|
|
259
237
|
this.off("failed", onFailed)
|
|
260
|
-
this._pendingWaits.delete(uuid)
|
|
261
238
|
this._subClient?.unsubscribe(channel).catch(() => {})
|
|
262
239
|
}
|
|
263
240
|
|
|
@@ -269,8 +246,6 @@ export default class Queue extends EventEmitter {
|
|
|
269
246
|
this.on("complete", onComplete)
|
|
270
247
|
this.on("failed", onFailed)
|
|
271
248
|
|
|
272
|
-
this._pendingWaits.set(uuid, true)
|
|
273
|
-
|
|
274
249
|
this._ensureSubClient().then((sub) => {
|
|
275
250
|
if (settled) { resolveReady(); return }
|
|
276
251
|
sub.subscribe(channel, (message) => {
|
|
@@ -330,10 +305,12 @@ export default class Queue extends EventEmitter {
|
|
|
330
305
|
if (this._subClient?.isOpen) await this._subClient.disconnect().catch(() => {})
|
|
331
306
|
this._subClient = null
|
|
332
307
|
if (this._redis.isOpen) await this._redis.quit()
|
|
308
|
+
if (this._semaphore) await this._semaphore.close().catch(() => {})
|
|
333
309
|
}
|
|
334
310
|
|
|
335
311
|
async _initialize() {
|
|
336
312
|
await this._redis.connect()
|
|
313
|
+
if (this._semaphore) await this._semaphore.peek("queue:active").catch(() => {})
|
|
337
314
|
await this._startWorkers()
|
|
338
315
|
if (this._options.concurrency > 0) {
|
|
339
316
|
await this._subscribeToGroupNotifications()
|
|
@@ -467,14 +444,11 @@ export default class Queue extends EventEmitter {
|
|
|
467
444
|
}
|
|
468
445
|
|
|
469
446
|
async _acquireGlobal(workerId, activeMap) {
|
|
470
|
-
const leaseId = randomUUID()
|
|
471
447
|
while (activeMap.get(workerId) && !this._closed) {
|
|
472
448
|
if (!this._redis.isOpen) return null
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
})
|
|
477
|
-
if (acquired) {
|
|
449
|
+
const result = await this._semaphore.acquire("queue:active")
|
|
450
|
+
if (result.acquired) {
|
|
451
|
+
const leaseId = result.id
|
|
478
452
|
this._activeLeases.add(leaseId)
|
|
479
453
|
const heartbeat = setInterval(() => this._renewGlobal(leaseId).catch(() => {}), HEARTBEAT_INTERVAL)
|
|
480
454
|
heartbeat.unref()
|
|
@@ -493,21 +467,11 @@ export default class Queue extends EventEmitter {
|
|
|
493
467
|
clearInterval(heartbeat)
|
|
494
468
|
this._heartbeats.delete(leaseId)
|
|
495
469
|
}
|
|
496
|
-
|
|
497
|
-
await this._redis.eval(RELEASE_SCRIPT, {
|
|
498
|
-
keys: ["queue:active"],
|
|
499
|
-
arguments: [leaseId],
|
|
500
|
-
})
|
|
501
|
-
}
|
|
470
|
+
await this._semaphore.release("queue:active", leaseId).catch(() => {})
|
|
502
471
|
}
|
|
503
472
|
|
|
504
473
|
async _renewGlobal(leaseId) {
|
|
505
|
-
|
|
506
|
-
await this._redis.eval(RENEW_SCRIPT, {
|
|
507
|
-
keys: ["queue:active"],
|
|
508
|
-
arguments: [leaseId],
|
|
509
|
-
})
|
|
510
|
-
}
|
|
474
|
+
await this._semaphore.renew("queue:active", leaseId).catch(() => {})
|
|
511
475
|
}
|
|
512
476
|
|
|
513
477
|
async _processTask(task, opts) {
|
|
@@ -592,6 +556,7 @@ export default class Queue extends EventEmitter {
|
|
|
592
556
|
}
|
|
593
557
|
this._groupInFlight.delete(groupKey)
|
|
594
558
|
}
|
|
559
|
+
this._workerClients = this._workerClients.filter((c) => c.isOpen)
|
|
595
560
|
} catch {}
|
|
596
561
|
}
|
|
597
562
|
}
|
package/types/queue.d.ts
CHANGED
|
@@ -2348,6 +2348,7 @@ export default class Queue extends EventEmitter<[never]> {
|
|
|
2348
2348
|
};
|
|
2349
2349
|
};
|
|
2350
2350
|
} & import("redis").RedisModules, import("redis").RedisFunctions, import("redis").RedisScripts, import("redis").RespVersions, import("redis").TypeMapping>;
|
|
2351
|
+
_semaphore: any;
|
|
2351
2352
|
_subClient: import("@redis/client").RedisClientType<{
|
|
2352
2353
|
json: {
|
|
2353
2354
|
ARRAPPEND: {
|
|
@@ -6976,7 +6977,6 @@ export default class Queue extends EventEmitter<[never]> {
|
|
|
6976
6977
|
};
|
|
6977
6978
|
};
|
|
6978
6979
|
} & import("redis").RedisModules, import("redis").RedisFunctions, import("redis").RedisScripts, import("redis").RespVersions, import("redis").TypeMapping>;
|
|
6979
|
-
_pendingWaits: Map<any, any>;
|
|
6980
6980
|
_readyPromise: Promise<void>;
|
|
6981
6981
|
/** @returns {Promise<void>} */
|
|
6982
6982
|
ready(): Promise<void>;
|
|
@@ -9333,7 +9333,7 @@ export default class Queue extends EventEmitter<[never]> {
|
|
|
9333
9333
|
_startWorker(workerId: any): Promise<void>;
|
|
9334
9334
|
_startGroupWorker(workerId: any, groupKey: any): Promise<void>;
|
|
9335
9335
|
_runWorkerLoop(workerId: any, client: any, key: any, activeMap: any, opts: any): Promise<void>;
|
|
9336
|
-
_acquireGlobal(workerId: any, activeMap: any): Promise
|
|
9336
|
+
_acquireGlobal(workerId: any, activeMap: any): Promise<any>;
|
|
9337
9337
|
_releaseGlobal(leaseId: any): Promise<void>;
|
|
9338
9338
|
_renewGlobal(leaseId: any): Promise<void>;
|
|
9339
9339
|
_processTask(task: any, opts: any): Promise<void>;
|