@prsm/queue 3.0.3 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/queue.js +16 -48
- package/types/queue.d.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prsm/queue",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.7",
|
|
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.1",
|
|
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,6 +102,14 @@ 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
115
|
this._readyPromise = this._initialize()
|
|
@@ -326,10 +305,12 @@ export default class Queue extends EventEmitter {
|
|
|
326
305
|
if (this._subClient?.isOpen) await this._subClient.disconnect().catch(() => {})
|
|
327
306
|
this._subClient = null
|
|
328
307
|
if (this._redis.isOpen) await this._redis.quit()
|
|
308
|
+
if (this._semaphore) await this._semaphore.close().catch(() => {})
|
|
329
309
|
}
|
|
330
310
|
|
|
331
311
|
async _initialize() {
|
|
332
312
|
await this._redis.connect()
|
|
313
|
+
if (this._semaphore) await this._semaphore.peek("queue:active").catch(() => {})
|
|
333
314
|
await this._startWorkers()
|
|
334
315
|
if (this._options.concurrency > 0) {
|
|
335
316
|
await this._subscribeToGroupNotifications()
|
|
@@ -463,14 +444,11 @@ export default class Queue extends EventEmitter {
|
|
|
463
444
|
}
|
|
464
445
|
|
|
465
446
|
async _acquireGlobal(workerId, activeMap) {
|
|
466
|
-
const leaseId = randomUUID()
|
|
467
447
|
while (activeMap.get(workerId) && !this._closed) {
|
|
468
448
|
if (!this._redis.isOpen) return null
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
})
|
|
473
|
-
if (acquired) {
|
|
449
|
+
const result = await this._semaphore.acquire("queue:active")
|
|
450
|
+
if (result.acquired) {
|
|
451
|
+
const leaseId = result.id
|
|
474
452
|
this._activeLeases.add(leaseId)
|
|
475
453
|
const heartbeat = setInterval(() => this._renewGlobal(leaseId).catch(() => {}), HEARTBEAT_INTERVAL)
|
|
476
454
|
heartbeat.unref()
|
|
@@ -489,21 +467,11 @@ export default class Queue extends EventEmitter {
|
|
|
489
467
|
clearInterval(heartbeat)
|
|
490
468
|
this._heartbeats.delete(leaseId)
|
|
491
469
|
}
|
|
492
|
-
|
|
493
|
-
await this._redis.eval(RELEASE_SCRIPT, {
|
|
494
|
-
keys: ["queue:active"],
|
|
495
|
-
arguments: [leaseId],
|
|
496
|
-
})
|
|
497
|
-
}
|
|
470
|
+
await this._semaphore.release("queue:active", leaseId).catch(() => {})
|
|
498
471
|
}
|
|
499
472
|
|
|
500
473
|
async _renewGlobal(leaseId) {
|
|
501
|
-
|
|
502
|
-
await this._redis.eval(RENEW_SCRIPT, {
|
|
503
|
-
keys: ["queue:active"],
|
|
504
|
-
arguments: [leaseId],
|
|
505
|
-
})
|
|
506
|
-
}
|
|
474
|
+
await this._semaphore.renew("queue:active", leaseId).catch(() => {})
|
|
507
475
|
}
|
|
508
476
|
|
|
509
477
|
async _processTask(task, opts) {
|
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: {
|
|
@@ -9332,7 +9333,7 @@ export default class Queue extends EventEmitter<[never]> {
|
|
|
9332
9333
|
_startWorker(workerId: any): Promise<void>;
|
|
9333
9334
|
_startGroupWorker(workerId: any, groupKey: any): Promise<void>;
|
|
9334
9335
|
_runWorkerLoop(workerId: any, client: any, key: any, activeMap: any, opts: any): Promise<void>;
|
|
9335
|
-
_acquireGlobal(workerId: any, activeMap: any): Promise
|
|
9336
|
+
_acquireGlobal(workerId: any, activeMap: any): Promise<any>;
|
|
9336
9337
|
_releaseGlobal(leaseId: any): Promise<void>;
|
|
9337
9338
|
_renewGlobal(leaseId: any): Promise<void>;
|
|
9338
9339
|
_processTask(task: any, opts: any): Promise<void>;
|