bullmq 5.80.7 → 5.80.9
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/dist/cjs/classes/flow-producer.js +32 -4
- package/dist/cjs/classes/job.js +9 -2
- package/dist/cjs/commands/addJobScheduler-11.lua +21 -13
- package/dist/cjs/scripts/addJobScheduler-11.js +20 -12
- package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/classes/flow-producer.d.ts +7 -0
- package/dist/esm/classes/flow-producer.js +32 -4
- package/dist/esm/classes/job.d.ts +1 -1
- package/dist/esm/classes/job.js +9 -2
- package/dist/esm/classes/queue-base.d.ts +1 -1
- package/dist/esm/commands/addJobScheduler-11.lua +21 -13
- package/dist/esm/interfaces/minimal-queue.d.ts +9 -0
- package/dist/esm/scripts/addJobScheduler-11.js +20 -12
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FlowProducer = void 0;
|
|
4
4
|
const events_1 = require("events");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
|
+
const create_scripts_1 = require("../utils/create-scripts");
|
|
6
7
|
const job_1 = require("./job");
|
|
7
8
|
const queue_keys_1 = require("./queue-keys");
|
|
8
9
|
const redis_connection_1 = require("./redis-connection");
|
|
@@ -19,6 +20,13 @@ class FlowProducer extends events_1.EventEmitter {
|
|
|
19
20
|
constructor(opts = { connection: {} }, Connection = redis_connection_1.RedisConnection) {
|
|
20
21
|
super();
|
|
21
22
|
this.opts = opts;
|
|
23
|
+
/**
|
|
24
|
+
* Cache of lightweight queue-like objects keyed by `${prefix}:${queueName}`.
|
|
25
|
+
* Each entry carries a single shared Scripts instance that is reused by all
|
|
26
|
+
* jobs added to that queue during the FlowProducer's lifetime, avoiding a
|
|
27
|
+
* redundant Scripts allocation per node/job.
|
|
28
|
+
*/
|
|
29
|
+
this.queues = new Map();
|
|
22
30
|
this.opts = Object.assign({ prefix: 'bull' }, opts);
|
|
23
31
|
this.connection = new Connection(opts.connection, {
|
|
24
32
|
shared: (0, utils_1.isRedisInstance)(opts.connection),
|
|
@@ -28,6 +36,7 @@ class FlowProducer extends events_1.EventEmitter {
|
|
|
28
36
|
});
|
|
29
37
|
this.connection.on('error', (error) => this.emit('error', error));
|
|
30
38
|
this.connection.on('close', () => {
|
|
39
|
+
this.queues.clear();
|
|
31
40
|
if (!this.closing) {
|
|
32
41
|
this.emit('ioredis:close');
|
|
33
42
|
}
|
|
@@ -351,22 +360,40 @@ class FlowProducer extends events_1.EventEmitter {
|
|
|
351
360
|
* @returns A queue-like object with the client, keys, and options needed to create jobs.
|
|
352
361
|
*/
|
|
353
362
|
queueFromNode(node, queueKeys, prefix) {
|
|
354
|
-
|
|
363
|
+
const cacheKey = `${prefix}:${node.queueName}`;
|
|
364
|
+
const cached = this.queues.get(cacheKey);
|
|
365
|
+
if (cached) {
|
|
366
|
+
return cached;
|
|
367
|
+
}
|
|
368
|
+
const flowProducer = this;
|
|
369
|
+
const queue = {
|
|
355
370
|
client: this.connection.client,
|
|
356
371
|
name: node.queueName,
|
|
357
372
|
keys: queueKeys.getKeys(node.queueName),
|
|
358
373
|
toKey: (type) => queueKeys.toKey(node.queueName, type),
|
|
359
374
|
opts: { prefix, connection: {} },
|
|
360
375
|
qualifiedName: queueKeys.getQueueQualifiedName(node.queueName),
|
|
361
|
-
closing
|
|
376
|
+
get closing() {
|
|
377
|
+
return flowProducer.closing;
|
|
378
|
+
},
|
|
362
379
|
waitUntilReady: async () => this.connection.client,
|
|
363
380
|
removeListener: this.removeListener.bind(this),
|
|
364
381
|
emit: this.emit.bind(this),
|
|
365
382
|
on: this.on.bind(this),
|
|
366
|
-
redisVersion
|
|
367
|
-
|
|
383
|
+
get redisVersion() {
|
|
384
|
+
return flowProducer.connection.redisVersion;
|
|
385
|
+
},
|
|
386
|
+
get databaseType() {
|
|
387
|
+
return flowProducer.connection.databaseType;
|
|
388
|
+
},
|
|
368
389
|
trace: async () => { },
|
|
369
390
|
};
|
|
391
|
+
// Build the shared Scripts instance once per queue so that every job
|
|
392
|
+
// created from this queue-like object reuses it instead of allocating
|
|
393
|
+
// its own.
|
|
394
|
+
queue.scripts = (0, create_scripts_1.createScripts)(queue);
|
|
395
|
+
this.queues.set(cacheKey, queue);
|
|
396
|
+
return queue;
|
|
370
397
|
}
|
|
371
398
|
/**
|
|
372
399
|
* Translates numeric addJob Lua error codes returned by root flow exec.
|
|
@@ -395,6 +422,7 @@ class FlowProducer extends events_1.EventEmitter {
|
|
|
395
422
|
*/
|
|
396
423
|
async close() {
|
|
397
424
|
if (!this.closing) {
|
|
425
|
+
this.queues.clear();
|
|
398
426
|
this.closing = this.connection.close();
|
|
399
427
|
}
|
|
400
428
|
await this.closing;
|
package/dist/cjs/classes/job.js
CHANGED
|
@@ -243,7 +243,13 @@ class Job {
|
|
|
243
243
|
return job;
|
|
244
244
|
}
|
|
245
245
|
createScripts() {
|
|
246
|
-
|
|
246
|
+
var _a;
|
|
247
|
+
// Reuse the queue's long-lived Scripts instance. It is bound to the same
|
|
248
|
+
// queue keys and connection, so a per-Job instance is pure duplication.
|
|
249
|
+
// The built-in Queue, Worker and FlowProducer always provide one; the
|
|
250
|
+
// fallback only covers custom queue-like objects that don't carry a
|
|
251
|
+
// Scripts instance.
|
|
252
|
+
this.scripts = (_a = this.queue.scripts) !== null && _a !== void 0 ? _a : (0, create_scripts_1.createScripts)(this.queue);
|
|
247
253
|
}
|
|
248
254
|
static optsFromJSON(rawOpts, optsDecode = utils_1.optsDecodeMap) {
|
|
249
255
|
const opts = JSON.parse(rawOpts || '{}');
|
|
@@ -297,7 +303,8 @@ class Job {
|
|
|
297
303
|
* @returns The total number of log entries for this job so far.
|
|
298
304
|
*/
|
|
299
305
|
static addJobLog(queue, jobId, logRow, keepLogs) {
|
|
300
|
-
|
|
306
|
+
var _a;
|
|
307
|
+
const scripts = (_a = queue.scripts) !== null && _a !== void 0 ? _a : (0, create_scripts_1.createScripts)(queue);
|
|
301
308
|
return scripts.addLog(jobId, logRow, keepLogs);
|
|
302
309
|
}
|
|
303
310
|
toJSON() {
|
|
@@ -144,23 +144,31 @@ end
|
|
|
144
144
|
local jobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
|
|
145
145
|
local jobKey = prefixKey .. jobId
|
|
146
146
|
|
|
147
|
-
-- If there's already a job with this ID, in a state
|
|
148
|
-
-- that is not updatable (active, completed, failed) we must
|
|
147
|
+
-- If there's already a job with this ID, in a state
|
|
148
|
+
-- that is not updatable (active, completed, failed) we must
|
|
149
149
|
-- handle the collision
|
|
150
150
|
local hasCollision = false
|
|
151
151
|
if rcall("EXISTS", jobKey) == 1 then
|
|
152
152
|
if every then
|
|
153
|
-
-- For 'every' case:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
153
|
+
-- For 'every' case: walk forward through subsequent slots
|
|
154
|
+
-- until we find a free one. Stale completed/failed jobs from
|
|
155
|
+
-- a previous scheduler under the same id can occupy several
|
|
156
|
+
-- consecutive slots (issue #3063), so a single retry is not
|
|
157
|
+
-- enough. The scan is bounded so we don't spin if the
|
|
158
|
+
-- scheduler is genuinely contested.
|
|
159
|
+
local maxSlotScans = 32
|
|
160
|
+
local slotsScanned = 0
|
|
161
|
+
local jobExists
|
|
162
|
+
repeat
|
|
163
|
+
nextMillis = nextMillis + every
|
|
164
|
+
jobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
|
|
165
|
+
jobKey = prefixKey .. jobId
|
|
166
|
+
slotsScanned = slotsScanned + 1
|
|
167
|
+
jobExists = rcall("EXISTS", jobKey)
|
|
168
|
+
until jobExists == 0 or slotsScanned >= maxSlotScans
|
|
169
|
+
|
|
170
|
+
if jobExists == 1 then
|
|
171
|
+
-- Every scanned slot still has a job, return error code
|
|
164
172
|
return -11 -- SchedulerJobSlotsBusy
|
|
165
173
|
end
|
|
166
174
|
else
|
|
@@ -553,22 +553,30 @@ end
|
|
|
553
553
|
-- Check for job ID collision with existing jobs (in any state)
|
|
554
554
|
local jobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
|
|
555
555
|
local jobKey = prefixKey .. jobId
|
|
556
|
-
-- If there's already a job with this ID, in a state
|
|
557
|
-
-- that is not updatable (active, completed, failed) we must
|
|
556
|
+
-- If there's already a job with this ID, in a state
|
|
557
|
+
-- that is not updatable (active, completed, failed) we must
|
|
558
558
|
-- handle the collision
|
|
559
559
|
local hasCollision = false
|
|
560
560
|
if rcall("EXISTS", jobKey) == 1 then
|
|
561
561
|
if every then
|
|
562
|
-
-- For 'every' case:
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
562
|
+
-- For 'every' case: walk forward through subsequent slots
|
|
563
|
+
-- until we find a free one. Stale completed/failed jobs from
|
|
564
|
+
-- a previous scheduler under the same id can occupy several
|
|
565
|
+
-- consecutive slots (issue #3063), so a single retry is not
|
|
566
|
+
-- enough. The scan is bounded so we don't spin if the
|
|
567
|
+
-- scheduler is genuinely contested.
|
|
568
|
+
local maxSlotScans = 32
|
|
569
|
+
local slotsScanned = 0
|
|
570
|
+
local jobExists
|
|
571
|
+
repeat
|
|
572
|
+
nextMillis = nextMillis + every
|
|
573
|
+
jobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
|
|
574
|
+
jobKey = prefixKey .. jobId
|
|
575
|
+
slotsScanned = slotsScanned + 1
|
|
576
|
+
jobExists = rcall("EXISTS", jobKey)
|
|
577
|
+
until jobExists == 0 or slotsScanned >= maxSlotScans
|
|
578
|
+
if jobExists == 1 then
|
|
579
|
+
-- Every scanned slot still has a job, return error code
|
|
572
580
|
return -11 -- SchedulerJobSlotsBusy
|
|
573
581
|
end
|
|
574
582
|
else
|