power-queues 2.0.4 → 2.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/dist/index.cjs +552 -526
- package/dist/index.d.cts +79 -13
- package/dist/index.d.ts +79 -13
- package/dist/index.js +552 -526
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -25,9 +25,8 @@ __export(index_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
27
|
// src/PowerQueues.ts
|
|
28
|
+
var import_power_redis = require("power-redis");
|
|
28
29
|
var import_full_utils = require("full-utils");
|
|
29
|
-
|
|
30
|
-
// src/AddTasks.ts
|
|
31
30
|
var import_uuid = require("uuid");
|
|
32
31
|
|
|
33
32
|
// src/scripts.ts
|
|
@@ -256,593 +255,620 @@ var SelectStuck = `
|
|
|
256
255
|
return results
|
|
257
256
|
`;
|
|
258
257
|
|
|
259
|
-
// src/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
258
|
+
// src/PowerQueues.ts
|
|
259
|
+
var PowerQueues = class extends import_power_redis.PowerRedis {
|
|
260
|
+
constructor() {
|
|
261
|
+
super(...arguments);
|
|
262
|
+
this.abort = new AbortController();
|
|
263
|
+
this.strictCheckingConnection = ["true", "on", "yes", "y", "1"].includes(String(process.env.REDIS_STRICT_CHECK_CONNECTION ?? "").trim().toLowerCase());
|
|
264
|
+
this.scripts = {};
|
|
265
|
+
this.addingBatchTasksCount = 800;
|
|
266
|
+
this.addingBatchKeysLimit = 1e4;
|
|
267
|
+
this.workerExecuteLockTimeoutMs = 18e4;
|
|
268
|
+
this.workerCacheTaskTimeoutMs = 60;
|
|
269
|
+
this.approveBatchTasksCount = 2e3;
|
|
270
|
+
this.removeOnExecuted = false;
|
|
271
|
+
this.executeBatchAtOnce = false;
|
|
272
|
+
this.executeJobStatus = false;
|
|
273
|
+
this.executeJobStatusTtlSec = 300;
|
|
274
|
+
this.consumerHost = "host";
|
|
275
|
+
this.stream = "stream";
|
|
276
|
+
this.group = "group";
|
|
277
|
+
this.workerBatchTasksCount = 200;
|
|
278
|
+
this.recoveryStuckTasksTimeoutMs = 6e4;
|
|
279
|
+
this.workerLoopIntervalMs = 5e3;
|
|
280
|
+
this.workerSelectionTimeoutMs = 80;
|
|
281
|
+
this.workerMaxRetries = 5;
|
|
282
|
+
this.workerClearAttemptsTimeoutMs = 864e5;
|
|
283
|
+
this.workerStatusTimeoutMs = 864e5;
|
|
284
|
+
}
|
|
285
|
+
async onSelected(data) {
|
|
286
|
+
return data;
|
|
287
|
+
}
|
|
288
|
+
async onExecute(id, payload, createdAt, job, key, attempt) {
|
|
289
|
+
}
|
|
290
|
+
async onExecuted(data) {
|
|
291
|
+
}
|
|
292
|
+
async onSuccess(id, payload, createdAt, job, key) {
|
|
293
|
+
}
|
|
294
|
+
async runQueue() {
|
|
295
|
+
await this.createGroup("0-0");
|
|
296
|
+
await this.consumerLoop();
|
|
297
|
+
}
|
|
298
|
+
async consumerLoop() {
|
|
299
|
+
const signal = this.signal();
|
|
300
|
+
while (!signal?.aborted) {
|
|
282
301
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
302
|
+
const tasks = await this.select();
|
|
303
|
+
if (!Array.isArray(tasks) || !(tasks.length > 0)) {
|
|
304
|
+
await (0, import_full_utils.wait)(600);
|
|
305
|
+
continue;
|
|
288
306
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const scripts = full ? [
|
|
294
|
-
["XAddBulk", XAddBulk],
|
|
295
|
-
["Approve", Approve],
|
|
296
|
-
["IdempotencyAllow", IdempotencyAllow],
|
|
297
|
-
["IdempotencyStart", IdempotencyStart],
|
|
298
|
-
["IdempotencyDone", IdempotencyDone],
|
|
299
|
-
["IdempotencyFree", IdempotencyFree],
|
|
300
|
-
["SelectStuck", SelectStuck]
|
|
301
|
-
] : [
|
|
302
|
-
["XAddBulk", XAddBulk]
|
|
303
|
-
];
|
|
304
|
-
for (const [name, code] of scripts) {
|
|
305
|
-
await this.loadScript(this.saveScript(name, code));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
async loadScript(code) {
|
|
309
|
-
for (let i = 0; i < 3; i++) {
|
|
310
|
-
try {
|
|
311
|
-
return await this.redis.script("LOAD", code);
|
|
312
|
-
} catch (e) {
|
|
313
|
-
if (i === 2) {
|
|
314
|
-
throw e;
|
|
315
|
-
}
|
|
316
|
-
await new Promise((r) => setTimeout(r, 10 + Math.floor(Math.random() * 40)));
|
|
307
|
+
const tasksP = await this.onSelected(tasks);
|
|
308
|
+
const ids = await this.execute(Array.isArray(tasksP) && tasksP.length > 0 ? tasksP : tasks);
|
|
309
|
+
if (Array.isArray(ids) && ids.length > 0) {
|
|
310
|
+
await this.approve(ids);
|
|
317
311
|
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
await this.batchError(err);
|
|
314
|
+
await (0, import_full_utils.wait)(600);
|
|
318
315
|
}
|
|
319
|
-
throw new Error("Load lua script failed.");
|
|
320
|
-
}
|
|
321
|
-
saveScript(name, codeBody) {
|
|
322
|
-
if (typeof codeBody !== "string" || !(codeBody.length > 0)) {
|
|
323
|
-
throw new Error("Script body is empty.");
|
|
324
|
-
}
|
|
325
|
-
this.scripts[name] = { codeBody };
|
|
326
|
-
return codeBody;
|
|
327
316
|
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const result = new Array(data.length);
|
|
350
|
-
const promises = [];
|
|
351
|
-
let cursor = 0;
|
|
352
|
-
for (const batch of batches) {
|
|
353
|
-
const start = cursor;
|
|
354
|
-
const end = start + batch.length;
|
|
355
|
-
cursor = end;
|
|
356
|
-
promises.push(async () => {
|
|
357
|
-
const partIds = await this.xaddBatch(queueName, ...this.payloadBatch(batch, opts));
|
|
358
|
-
for (let k = 0; k < partIds.length; k++) {
|
|
359
|
-
result[start + k] = partIds[k];
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
const runners = Array.from({ length: promises.length }, async () => {
|
|
364
|
-
while (promises.length) {
|
|
365
|
-
const promise = promises.shift();
|
|
366
|
-
if (promise) {
|
|
367
|
-
await promise();
|
|
368
|
-
}
|
|
317
|
+
}
|
|
318
|
+
async addTasks(queueName, data, opts = {}) {
|
|
319
|
+
if (!Array.isArray(data) || !(data.length > 0)) {
|
|
320
|
+
throw new Error("Tasks is not filled.");
|
|
321
|
+
}
|
|
322
|
+
if (typeof queueName !== "string" || !(queueName.length > 0)) {
|
|
323
|
+
throw new Error("Queue name is required.");
|
|
324
|
+
}
|
|
325
|
+
const job = (0, import_uuid.v4)();
|
|
326
|
+
const batches = this.buildBatches(data, job, opts.idem);
|
|
327
|
+
const result = new Array(data.length);
|
|
328
|
+
const promises = [];
|
|
329
|
+
let cursor = 0;
|
|
330
|
+
for (const batch of batches) {
|
|
331
|
+
const start = cursor;
|
|
332
|
+
const end = start + batch.length;
|
|
333
|
+
cursor = end;
|
|
334
|
+
promises.push(async () => {
|
|
335
|
+
const partIds = await this.xaddBatch(queueName, ...this.payloadBatch(batch, opts));
|
|
336
|
+
for (let k = 0; k < partIds.length; k++) {
|
|
337
|
+
result[start + k] = partIds[k];
|
|
369
338
|
}
|
|
370
339
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
payloadBatch(data, opts) {
|
|
378
|
-
const maxlen = Math.max(0, Math.floor(opts?.maxlen ?? 0));
|
|
379
|
-
const approx = opts?.exact ? 0 : opts?.approx !== false ? 1 : 0;
|
|
380
|
-
const exact = opts?.exact ? 1 : 0;
|
|
381
|
-
const nomkstream = opts?.nomkstream ? 1 : 0;
|
|
382
|
-
const trimLimit = Math.max(0, Math.floor(opts?.trimLimit ?? 0));
|
|
383
|
-
const minidWindowMs = Math.max(0, Math.floor(opts?.minidWindowMs ?? 0));
|
|
384
|
-
const minidExact = opts?.minidExact ? 1 : 0;
|
|
385
|
-
const argv = [
|
|
386
|
-
String(maxlen),
|
|
387
|
-
String(approx),
|
|
388
|
-
String(data.length),
|
|
389
|
-
String(exact),
|
|
390
|
-
String(nomkstream),
|
|
391
|
-
String(trimLimit),
|
|
392
|
-
String(minidWindowMs),
|
|
393
|
-
String(minidExact)
|
|
394
|
-
];
|
|
395
|
-
for (const item of data) {
|
|
396
|
-
const entry = item;
|
|
397
|
-
const id = entry.id ?? "*";
|
|
398
|
-
let flat;
|
|
399
|
-
if ("flat" in entry && Array.isArray(entry.flat) && entry.flat.length > 0) {
|
|
400
|
-
flat = entry.flat;
|
|
401
|
-
if (flat.length % 2 !== 0) {
|
|
402
|
-
throw new Error('Property "flat" must contain an even number of realKeysLength (field/value pairs).');
|
|
403
|
-
}
|
|
404
|
-
} else if ("payload" in entry && typeof entry.payload === "object" && Object.keys(entry.payload || {}).length > 0) {
|
|
405
|
-
flat = [];
|
|
406
|
-
for (const [k, v] of Object.entries(entry.payload)) {
|
|
407
|
-
flat.push(k, v);
|
|
408
|
-
}
|
|
409
|
-
} else {
|
|
410
|
-
throw new Error('Task must have "payload" or "flat".');
|
|
411
|
-
}
|
|
412
|
-
const pairs = flat.length / 2;
|
|
413
|
-
if (pairs <= 0) {
|
|
414
|
-
throw new Error('Task must have "payload" or "flat".');
|
|
415
|
-
}
|
|
416
|
-
argv.push(String(id));
|
|
417
|
-
argv.push(String(pairs));
|
|
418
|
-
for (const token of flat) {
|
|
419
|
-
argv.push(!token ? "" : typeof token === "string" && token.length > 0 ? token : String(token));
|
|
340
|
+
}
|
|
341
|
+
const runners = Array.from({ length: promises.length }, async () => {
|
|
342
|
+
while (promises.length) {
|
|
343
|
+
const promise = promises.shift();
|
|
344
|
+
if (promise) {
|
|
345
|
+
await promise();
|
|
420
346
|
}
|
|
421
347
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
realKeysLength = 0;
|
|
348
|
+
});
|
|
349
|
+
if (opts.status) {
|
|
350
|
+
await this.redis.set(`${queueName}:${job}:total`, data.length);
|
|
351
|
+
await this.redis.set(`${queueName}:${job}:ready`, 0);
|
|
352
|
+
await this.redis.set(`${queueName}:${job}:err`, 0);
|
|
353
|
+
await this.redis.set(`${queueName}:${job}:ok`, 0);
|
|
354
|
+
await this.redis.pexpire(`${queueName}:${job}:total`, this.workerStatusTimeoutMs);
|
|
355
|
+
await this.redis.pexpire(`${queueName}:${job}:ready`, this.workerStatusTimeoutMs);
|
|
356
|
+
await this.redis.pexpire(`${queueName}:${job}:err`, this.workerStatusTimeoutMs);
|
|
357
|
+
await this.redis.pexpire(`${queueName}:${job}:ok`, this.workerStatusTimeoutMs);
|
|
358
|
+
}
|
|
359
|
+
await Promise.all(runners);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
async loadScripts(full = false) {
|
|
363
|
+
const scripts = full ? [
|
|
364
|
+
["XAddBulk", XAddBulk],
|
|
365
|
+
["Approve", Approve],
|
|
366
|
+
["IdempotencyAllow", IdempotencyAllow],
|
|
367
|
+
["IdempotencyStart", IdempotencyStart],
|
|
368
|
+
["IdempotencyDone", IdempotencyDone],
|
|
369
|
+
["IdempotencyFree", IdempotencyFree],
|
|
370
|
+
["SelectStuck", SelectStuck]
|
|
371
|
+
] : [
|
|
372
|
+
["XAddBulk", XAddBulk]
|
|
373
|
+
];
|
|
374
|
+
for (const [name, code] of scripts) {
|
|
375
|
+
await this.loadScript(this.saveScript(name, code));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async loadScript(code) {
|
|
379
|
+
for (let i = 0; i < 3; i++) {
|
|
380
|
+
try {
|
|
381
|
+
return await this.redis.script("LOAD", code);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
if (i === 2) {
|
|
384
|
+
throw e;
|
|
460
385
|
}
|
|
461
|
-
|
|
462
|
-
realKeysLength += reqKeysLength;
|
|
386
|
+
await new Promise((r) => setTimeout(r, 10 + Math.floor(Math.random() * 40)));
|
|
463
387
|
}
|
|
464
|
-
if (batch.length) {
|
|
465
|
-
batches.push(batch);
|
|
466
|
-
}
|
|
467
|
-
return batches;
|
|
468
388
|
}
|
|
469
|
-
|
|
470
|
-
|
|
389
|
+
throw new Error("Load lua script failed.");
|
|
390
|
+
}
|
|
391
|
+
saveScript(name, codeBody) {
|
|
392
|
+
if (typeof codeBody !== "string" || !(codeBody.length > 0)) {
|
|
393
|
+
throw new Error("Script body is empty.");
|
|
471
394
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
super(...arguments);
|
|
480
|
-
this.consumerHost = "host";
|
|
481
|
-
this.stream = "stream";
|
|
482
|
-
this.group = "group";
|
|
483
|
-
this.workerBatchTasksCount = 200;
|
|
484
|
-
this.recoveryStuckTasksTimeoutMs = 6e4;
|
|
485
|
-
this.workerLoopIntervalMs = 5e3;
|
|
486
|
-
this.workerSelectionTimeoutMs = 80;
|
|
487
|
-
}
|
|
488
|
-
async createGroup(from = "$") {
|
|
489
|
-
try {
|
|
490
|
-
await this.redis.xgroup("CREATE", this.stream, this.group, from, "MKSTREAM");
|
|
491
|
-
} catch (err) {
|
|
492
|
-
const msg = String(err?.message || "");
|
|
493
|
-
if (!msg.includes("BUSYGROUP")) {
|
|
494
|
-
throw err;
|
|
495
|
-
}
|
|
395
|
+
this.scripts[name] = { codeBody };
|
|
396
|
+
return codeBody;
|
|
397
|
+
}
|
|
398
|
+
async runScript(name, keys, args, defaultCode) {
|
|
399
|
+
if (!this.scripts[name]) {
|
|
400
|
+
if (typeof defaultCode !== "string" || !(defaultCode.length > 0)) {
|
|
401
|
+
throw new Error(`Undefined script "${name}". Save it before executing.`);
|
|
496
402
|
}
|
|
403
|
+
this.saveScript(name, defaultCode);
|
|
497
404
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
405
|
+
if (!this.scripts[name].codeReady) {
|
|
406
|
+
this.scripts[name].codeReady = await this.loadScript(this.scripts[name].codeBody);
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
if (String(err?.message || "").includes("NOSCRIPT")) {
|
|
412
|
+
this.scripts[name].codeReady = await this.loadScript(this.scripts[name].codeBody);
|
|
413
|
+
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
502
414
|
}
|
|
503
|
-
|
|
415
|
+
throw err;
|
|
504
416
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
417
|
+
}
|
|
418
|
+
async xaddBatch(queueName, ...batches) {
|
|
419
|
+
return await this.runScript("XAddBulk", [queueName], batches, XAddBulk);
|
|
420
|
+
}
|
|
421
|
+
payloadBatch(data, opts) {
|
|
422
|
+
const maxlen = Math.max(0, Math.floor(opts?.maxlen ?? 0));
|
|
423
|
+
const approx = opts?.exact ? 0 : opts?.approx !== false ? 1 : 0;
|
|
424
|
+
const exact = opts?.exact ? 1 : 0;
|
|
425
|
+
const nomkstream = opts?.nomkstream ? 1 : 0;
|
|
426
|
+
const trimLimit = Math.max(0, Math.floor(opts?.trimLimit ?? 0));
|
|
427
|
+
const minidWindowMs = Math.max(0, Math.floor(opts?.minidWindowMs ?? 0));
|
|
428
|
+
const minidExact = opts?.minidExact ? 1 : 0;
|
|
429
|
+
const argv = [
|
|
430
|
+
String(maxlen),
|
|
431
|
+
String(approx),
|
|
432
|
+
String(data.length),
|
|
433
|
+
String(exact),
|
|
434
|
+
String(nomkstream),
|
|
435
|
+
String(trimLimit),
|
|
436
|
+
String(minidWindowMs),
|
|
437
|
+
String(minidExact)
|
|
438
|
+
];
|
|
439
|
+
for (const item of data) {
|
|
440
|
+
const entry = item;
|
|
441
|
+
const id = entry.id ?? "*";
|
|
442
|
+
let flat;
|
|
443
|
+
if ("flat" in entry && Array.isArray(entry.flat) && entry.flat.length > 0) {
|
|
444
|
+
flat = entry.flat;
|
|
445
|
+
if (flat.length % 2 !== 0) {
|
|
446
|
+
throw new Error('Property "flat" must contain an even number of realKeysLength (field/value pairs).');
|
|
447
|
+
}
|
|
448
|
+
} else if ("payload" in entry && typeof entry.payload === "object" && Object.keys(entry.payload || {}).length > 0) {
|
|
449
|
+
flat = [];
|
|
450
|
+
for (const [k, v] of Object.entries(entry.payload)) {
|
|
451
|
+
flat.push(k, v);
|
|
512
452
|
}
|
|
453
|
+
} else {
|
|
454
|
+
throw new Error('Task must have "payload" or "flat".');
|
|
455
|
+
}
|
|
456
|
+
const pairs = flat.length / 2;
|
|
457
|
+
if (pairs <= 0) {
|
|
458
|
+
throw new Error('Task must have "payload" or "flat".');
|
|
459
|
+
}
|
|
460
|
+
argv.push(String(id));
|
|
461
|
+
argv.push(String(pairs));
|
|
462
|
+
for (const token of flat) {
|
|
463
|
+
argv.push(!token ? "" : typeof token === "string" && token.length > 0 ? token : String(token));
|
|
513
464
|
}
|
|
514
|
-
return [];
|
|
515
465
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!entries?.length) {
|
|
536
|
-
return [];
|
|
466
|
+
return argv;
|
|
467
|
+
}
|
|
468
|
+
buildBatches(tasks, job, idem) {
|
|
469
|
+
const batches = [];
|
|
470
|
+
let batch = [], realKeysLength = 0;
|
|
471
|
+
for (let task of tasks) {
|
|
472
|
+
const createdAt = task?.createdAt || Date.now();
|
|
473
|
+
let entry = task;
|
|
474
|
+
if (typeof entry.payload === "object") {
|
|
475
|
+
entry = {
|
|
476
|
+
...entry,
|
|
477
|
+
payload: {
|
|
478
|
+
payload: JSON.stringify(entry.payload),
|
|
479
|
+
createdAt,
|
|
480
|
+
job
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
if (idem) {
|
|
484
|
+
entry.payload["idemKey"] = entry?.idemKey || (0, import_uuid.v4)();
|
|
537
485
|
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
486
|
+
} else if (Array.isArray(entry.flat)) {
|
|
487
|
+
entry.flat.push("createdAt");
|
|
488
|
+
entry.flat.push(String(createdAt));
|
|
489
|
+
entry.flat.push("job");
|
|
490
|
+
entry.flat.push(job);
|
|
491
|
+
if (idem) {
|
|
492
|
+
entry.flat.push("idemKey");
|
|
493
|
+
entry.flat.push(entry?.idemKey || (0, import_uuid.v4)());
|
|
541
494
|
}
|
|
542
495
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
496
|
+
const reqKeysLength = this.keysLength(entry);
|
|
497
|
+
if (batch.length && (batch.length >= this.addingBatchTasksCount || realKeysLength + reqKeysLength > this.addingBatchKeysLimit)) {
|
|
498
|
+
batches.push(batch);
|
|
499
|
+
batch = [];
|
|
500
|
+
realKeysLength = 0;
|
|
548
501
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const kvRaw = e?.[1] ?? [];
|
|
552
|
-
const kv = Array.isArray(kvRaw) ? kvRaw.map((x) => Buffer.isBuffer(x) ? x.toString() : x) : [];
|
|
553
|
-
return [id, kv, 0, "", ""];
|
|
554
|
-
}).filter(([id, kv]) => typeof id === "string" && id.length > 0 && Array.isArray(kv) && (kv.length & 1) === 0).map(([id, kv]) => {
|
|
555
|
-
const values = this.values(kv);
|
|
556
|
-
const { idemKey = "", createdAt, job, ...data } = this.payload(values);
|
|
557
|
-
return [id, data, createdAt, job, idemKey];
|
|
558
|
-
});
|
|
502
|
+
batch.push(entry);
|
|
503
|
+
realKeysLength += reqKeysLength;
|
|
559
504
|
}
|
|
560
|
-
|
|
561
|
-
|
|
505
|
+
if (batch.length) {
|
|
506
|
+
batches.push(batch);
|
|
562
507
|
}
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
this.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
508
|
+
return batches;
|
|
509
|
+
}
|
|
510
|
+
keysLength(task) {
|
|
511
|
+
return 2 + ("flat" in task && Array.isArray(task.flat) && task.flat.length ? task.flat.length : Object.keys(task).length * 2);
|
|
512
|
+
}
|
|
513
|
+
attemptsKey(id) {
|
|
514
|
+
const safeStream = this.stream.replace(/[^\w:\-]/g, "_");
|
|
515
|
+
const safeId = id.replace(/[^\w:\-]/g, "_");
|
|
516
|
+
return `q:${safeStream}:attempts:${safeId}`;
|
|
517
|
+
}
|
|
518
|
+
async incrAttempts(id) {
|
|
519
|
+
try {
|
|
520
|
+
const key = this.attemptsKey(id);
|
|
521
|
+
const attempts = await this.redis.incr(key);
|
|
522
|
+
await this.redis.pexpire(key, this.workerClearAttemptsTimeoutMs);
|
|
523
|
+
return attempts;
|
|
524
|
+
} catch (err) {
|
|
525
|
+
}
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
async getAttempts(id) {
|
|
529
|
+
const key = this.attemptsKey(id);
|
|
530
|
+
const v = await this.redis.get(key);
|
|
531
|
+
return Number(v || 0);
|
|
532
|
+
}
|
|
533
|
+
async clearAttempts(id) {
|
|
534
|
+
const key = this.attemptsKey(id);
|
|
535
|
+
try {
|
|
536
|
+
await this.redis.del(key);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async success(id, payload, createdAt, job, key) {
|
|
541
|
+
if (this.executeJobStatus) {
|
|
542
|
+
await this.status(id, payload, createdAt, job, key);
|
|
543
|
+
}
|
|
544
|
+
await this.onSuccess(id, payload, createdAt, job, key);
|
|
545
|
+
}
|
|
546
|
+
async status(id, payload, createdAt, job, key) {
|
|
547
|
+
const prefix = `s:${this.stream}:`;
|
|
548
|
+
const { ready = 0, ok = 0 } = await this.getMany(prefix);
|
|
549
|
+
await this.setMany([{ key: `${prefix}ready`, value: ready + 1 }, { key: `${prefix}ok`, value: ok + 1 }], this.executeJobStatusTtlSec);
|
|
550
|
+
}
|
|
551
|
+
async batchError(err, tasks) {
|
|
552
|
+
}
|
|
553
|
+
async error(err, id, payload, createdAt, job, key) {
|
|
554
|
+
await this.onError(err, id, payload, createdAt, job, key);
|
|
555
|
+
}
|
|
556
|
+
async onError(err, id, payload, createdAt, job, key) {
|
|
557
|
+
}
|
|
558
|
+
async attempt(err, id, payload, createdAt, job, key, attempts) {
|
|
559
|
+
await this.onRetry(err, id, payload, createdAt, job, key, attempts);
|
|
560
|
+
}
|
|
561
|
+
async onRetry(err, id, payload, createdAt, job, key, attempts) {
|
|
562
|
+
}
|
|
563
|
+
async execute(tasks) {
|
|
564
|
+
const result = [];
|
|
565
|
+
let contended = 0, promises = [];
|
|
566
|
+
for (const [id, payload, createdAt, job, idemKey] of tasks) {
|
|
567
|
+
if (this.executeBatchAtOnce) {
|
|
568
|
+
promises.push((async () => {
|
|
614
569
|
const r = await this.executeProcess(id, payload, createdAt, job, idemKey);
|
|
615
570
|
if (r.id) {
|
|
616
571
|
result.push(id);
|
|
617
572
|
} else if (r.contended) {
|
|
618
573
|
contended++;
|
|
619
574
|
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
try {
|
|
623
|
-
if (this.executeBatchAtOnce && promises.length > 0) {
|
|
624
|
-
await Promise.all(promises);
|
|
625
|
-
}
|
|
626
|
-
await this.onExecuted(tasks);
|
|
627
|
-
if ((!Array.isArray(result) || !(result.length > 0)) && contended > tasks.length >> 1) {
|
|
628
|
-
await this.waitAbortable(15 + Math.floor(Math.random() * 35) + Math.min(250, 15 * contended + Math.floor(Math.random() * 40)));
|
|
629
|
-
}
|
|
630
|
-
} catch (err) {
|
|
631
|
-
}
|
|
632
|
-
return result;
|
|
633
|
-
}
|
|
634
|
-
async executeProcess(id, payload, createdAt, job, key) {
|
|
635
|
-
if (key) {
|
|
636
|
-
return await this.idempotency(id, payload, createdAt, job, key);
|
|
575
|
+
})());
|
|
637
576
|
} else {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
577
|
+
const r = await this.executeProcess(id, payload, createdAt, job, idemKey);
|
|
578
|
+
if (r.id) {
|
|
579
|
+
result.push(id);
|
|
580
|
+
} else if (r.contended) {
|
|
581
|
+
contended++;
|
|
643
582
|
}
|
|
644
583
|
}
|
|
645
|
-
return {};
|
|
646
584
|
}
|
|
647
|
-
|
|
648
|
-
if (
|
|
649
|
-
|
|
585
|
+
try {
|
|
586
|
+
if (this.executeBatchAtOnce && promises.length > 0) {
|
|
587
|
+
await Promise.all(promises);
|
|
650
588
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const room = Math.min(approveBatchTasksCount, ids.length - i);
|
|
655
|
-
const part = ids.slice(i, i + room);
|
|
656
|
-
const approved = await this.runScript("Approve", [this.stream], [this.group, this.removeOnExecuted ? "1" : "0", ...part], Approve);
|
|
657
|
-
total += Number(approved || 0);
|
|
658
|
-
i += room;
|
|
589
|
+
await this.onExecuted(tasks);
|
|
590
|
+
if ((!Array.isArray(result) || !(result.length > 0)) && contended > tasks.length >> 1) {
|
|
591
|
+
await this.waitAbortable(15 + Math.floor(Math.random() * 35) + Math.min(250, 15 * contended + Math.floor(Math.random() * 40)));
|
|
659
592
|
}
|
|
660
|
-
|
|
593
|
+
} catch (err) {
|
|
594
|
+
await this.batchError(err, tasks);
|
|
661
595
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
let ttl = -2;
|
|
669
|
-
try {
|
|
670
|
-
ttl = await this.redis.pttl(keys.startKey);
|
|
671
|
-
} catch (err) {
|
|
672
|
-
}
|
|
673
|
-
await this.waitAbortable(ttl);
|
|
674
|
-
return { contended: true };
|
|
675
|
-
}
|
|
676
|
-
if (!await this.idempotencyStart(keys)) {
|
|
677
|
-
return { contended: true };
|
|
678
|
-
}
|
|
679
|
-
const heartbeat = this.heartbeat(keys) || (() => {
|
|
680
|
-
});
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
async executeProcess(id, payload, createdAt, job, key) {
|
|
599
|
+
if (key) {
|
|
600
|
+
return await this.idempotency(id, payload, createdAt, job, key);
|
|
601
|
+
} else {
|
|
681
602
|
try {
|
|
682
|
-
await this.onExecute(id, payload, createdAt, job, key);
|
|
683
|
-
await this.idempotencyDone(keys);
|
|
603
|
+
await this.onExecute(id, payload, createdAt, job, key, await this.getAttempts(id));
|
|
684
604
|
await this.success(id, payload, createdAt, job, key);
|
|
685
605
|
return { id };
|
|
686
606
|
} catch (err) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
607
|
+
const attempts = await this.incrAttempts(id);
|
|
608
|
+
await this.attempt(err, id, payload, createdAt, job, key, attempts);
|
|
609
|
+
await this.error(err, id, payload, createdAt, job, key);
|
|
610
|
+
if (attempts >= this.workerMaxRetries) {
|
|
611
|
+
await this.addTasks(`${this.stream}:dlq`, [{
|
|
612
|
+
payload: {
|
|
613
|
+
...payload,
|
|
614
|
+
error: String(err?.message || err),
|
|
615
|
+
createdAt,
|
|
616
|
+
job,
|
|
617
|
+
id,
|
|
618
|
+
attempts
|
|
619
|
+
}
|
|
620
|
+
}]);
|
|
621
|
+
await this.clearAttempts(id);
|
|
622
|
+
return { id };
|
|
690
623
|
}
|
|
691
|
-
} finally {
|
|
692
|
-
heartbeat();
|
|
693
624
|
}
|
|
694
625
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
return
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
await this.runScript("IdempotencyFree", [keys.lockKey, keys.startKey], [keys.token], IdempotencyFree);
|
|
723
|
-
}
|
|
724
|
-
async waitAbortable(ttl) {
|
|
725
|
-
return new Promise((resolve) => {
|
|
726
|
-
const signal = this.signal();
|
|
727
|
-
if (signal?.aborted) {
|
|
728
|
-
return resolve();
|
|
729
|
-
}
|
|
730
|
-
const t = setTimeout(() => {
|
|
731
|
-
if (signal) {
|
|
732
|
-
signal.removeEventListener("abort", onAbort);
|
|
733
|
-
}
|
|
734
|
-
resolve();
|
|
735
|
-
}, ttl > 0 ? 25 + Math.floor(Math.random() * 50) : 5 + Math.floor(Math.random() * 15));
|
|
736
|
-
t.unref?.();
|
|
737
|
-
function onAbort() {
|
|
738
|
-
clearTimeout(t);
|
|
739
|
-
resolve();
|
|
740
|
-
}
|
|
741
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
heartbeat(keys) {
|
|
745
|
-
if (this.workerExecuteLockTimeoutMs <= 0) {
|
|
746
|
-
return;
|
|
626
|
+
return {};
|
|
627
|
+
}
|
|
628
|
+
async approve(ids) {
|
|
629
|
+
if (!Array.isArray(ids) || !(ids.length > 0)) {
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
const approveBatchTasksCount = Math.max(500, Math.min(4e3, this.approveBatchTasksCount));
|
|
633
|
+
let total = 0, i = 0;
|
|
634
|
+
while (i < ids.length) {
|
|
635
|
+
const room = Math.min(approveBatchTasksCount, ids.length - i);
|
|
636
|
+
const part = ids.slice(i, i + room);
|
|
637
|
+
const approved = await this.runScript("Approve", [this.stream], [this.group, this.removeOnExecuted ? "1" : "0", ...part], Approve);
|
|
638
|
+
total += Number(approved || 0);
|
|
639
|
+
i += room;
|
|
640
|
+
}
|
|
641
|
+
return total;
|
|
642
|
+
}
|
|
643
|
+
async idempotency(id, payload, createdAt, job, key) {
|
|
644
|
+
const keys = this.idempotencyKeys(key);
|
|
645
|
+
const allow = await this.idempotencyAllow(keys);
|
|
646
|
+
if (allow === 1) {
|
|
647
|
+
return { id };
|
|
648
|
+
} else if (allow === 0) {
|
|
649
|
+
let ttl = -2;
|
|
650
|
+
try {
|
|
651
|
+
ttl = await this.redis.pttl(keys.startKey);
|
|
652
|
+
} catch (err) {
|
|
747
653
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
654
|
+
await this.waitAbortable(ttl);
|
|
655
|
+
return { contended: true };
|
|
656
|
+
}
|
|
657
|
+
if (!await this.idempotencyStart(keys)) {
|
|
658
|
+
return { contended: true };
|
|
659
|
+
}
|
|
660
|
+
const heartbeat = this.heartbeat(keys) || (() => {
|
|
661
|
+
});
|
|
662
|
+
try {
|
|
663
|
+
await this.onExecute(id, payload, createdAt, job, key, await this.getAttempts(id));
|
|
664
|
+
await this.idempotencyDone(keys);
|
|
665
|
+
await this.success(id, payload, createdAt, job, key);
|
|
666
|
+
return { id };
|
|
667
|
+
} catch (err) {
|
|
668
|
+
const attempts = await this.incrAttempts(id);
|
|
669
|
+
try {
|
|
670
|
+
await this.attempt(err, id, payload, createdAt, job, key, attempts);
|
|
671
|
+
await this.error(err, id, payload, createdAt, job, key);
|
|
672
|
+
if (attempts >= this.workerMaxRetries) {
|
|
673
|
+
await this.addTasks(`${this.stream}:dlq`, [{
|
|
674
|
+
payload: {
|
|
675
|
+
...payload,
|
|
676
|
+
error: String(err?.message || err),
|
|
677
|
+
createdAt,
|
|
678
|
+
job,
|
|
679
|
+
id
|
|
680
|
+
}
|
|
681
|
+
}]);
|
|
682
|
+
await this.clearAttempts(id);
|
|
683
|
+
await this.idempotencyFree(keys);
|
|
684
|
+
return { id };
|
|
775
685
|
}
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
779
|
-
return () => {
|
|
780
|
-
signal?.removeEventListener?.("abort", onAbort);
|
|
781
|
-
stop();
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
values(value) {
|
|
785
|
-
const result = {};
|
|
786
|
-
for (let i = 0; i < value.length; i += 2) {
|
|
787
|
-
result[value[i]] = value[i + 1];
|
|
686
|
+
await this.idempotencyFree(keys);
|
|
687
|
+
} catch (err2) {
|
|
788
688
|
}
|
|
789
|
-
|
|
689
|
+
} finally {
|
|
690
|
+
heartbeat();
|
|
790
691
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
692
|
+
}
|
|
693
|
+
idempotencyKeys(key) {
|
|
694
|
+
const prefix = `q:${this.stream.replace(/[^\w:\-]/g, "_")}:`;
|
|
695
|
+
const keyP = key.replace(/[^\w:\-]/g, "_");
|
|
696
|
+
const doneKey = `${prefix}done:${keyP}`;
|
|
697
|
+
const lockKey = `${prefix}lock:${keyP}`;
|
|
698
|
+
const startKey = `${prefix}start:${keyP}`;
|
|
699
|
+
const token = `${this.consumer()}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
700
|
+
return {
|
|
701
|
+
prefix,
|
|
702
|
+
doneKey,
|
|
703
|
+
lockKey,
|
|
704
|
+
startKey,
|
|
705
|
+
token
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async idempotencyAllow(keys) {
|
|
709
|
+
const res = await this.runScript("IdempotencyAllow", [keys.doneKey, keys.lockKey, keys.startKey], [String(this.workerExecuteLockTimeoutMs), keys.token], IdempotencyAllow);
|
|
710
|
+
return Number(res || 0);
|
|
711
|
+
}
|
|
712
|
+
async idempotencyStart(keys) {
|
|
713
|
+
const res = await this.runScript("IdempotencyStart", [keys.lockKey, keys.startKey], [keys.token, String(this.workerExecuteLockTimeoutMs)], IdempotencyStart);
|
|
714
|
+
return Number(res || 0) === 1;
|
|
715
|
+
}
|
|
716
|
+
async idempotencyDone(keys) {
|
|
717
|
+
await this.runScript("IdempotencyDone", [keys.doneKey, keys.lockKey, keys.startKey], [String(this.workerCacheTaskTimeoutMs), keys.token], IdempotencyDone);
|
|
718
|
+
}
|
|
719
|
+
async idempotencyFree(keys) {
|
|
720
|
+
await this.runScript("IdempotencyFree", [keys.lockKey, keys.startKey], [keys.token], IdempotencyFree);
|
|
721
|
+
}
|
|
722
|
+
async createGroup(from = "$") {
|
|
723
|
+
try {
|
|
724
|
+
await this.redis.xgroup("CREATE", this.stream, this.group, from, "MKSTREAM");
|
|
725
|
+
} catch (err) {
|
|
726
|
+
const msg = String(err?.message || "");
|
|
727
|
+
if (!msg.includes("BUSYGROUP")) {
|
|
728
|
+
throw err;
|
|
795
729
|
}
|
|
796
|
-
return data;
|
|
797
|
-
}
|
|
798
|
-
consumer() {
|
|
799
|
-
return `${String(this.consumerHost || "host")}:${process.pid}`;
|
|
800
|
-
}
|
|
801
|
-
signal() {
|
|
802
|
-
return this.abort.signal;
|
|
803
730
|
}
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// src/PowerQueues.ts
|
|
808
|
-
var Base = class {
|
|
809
|
-
};
|
|
810
|
-
var PowerQueues = class extends mix(Base, AddTasks, SelectTasks, ExecuteTasks) {
|
|
811
|
-
constructor() {
|
|
812
|
-
super(...arguments);
|
|
813
|
-
this.abort = new AbortController();
|
|
814
|
-
}
|
|
815
|
-
async onSelected(data) {
|
|
816
|
-
return data;
|
|
817
731
|
}
|
|
818
|
-
async
|
|
732
|
+
async select() {
|
|
733
|
+
let entries = await this.selectStuck();
|
|
734
|
+
if (!entries?.length) {
|
|
735
|
+
entries = await this.selectFresh();
|
|
736
|
+
}
|
|
737
|
+
return this.normalizeEntries(entries);
|
|
819
738
|
}
|
|
820
|
-
async
|
|
739
|
+
async selectStuck() {
|
|
740
|
+
try {
|
|
741
|
+
const res = await this.runScript("SelectStuck", [this.stream], [this.group, this.consumer(), String(this.recoveryStuckTasksTimeoutMs), String(this.workerBatchTasksCount), String(this.workerSelectionTimeoutMs)], SelectStuck);
|
|
742
|
+
return Array.isArray(res) ? res : [];
|
|
743
|
+
} catch (err) {
|
|
744
|
+
if (String(err?.message || "").includes("NOGROUP")) {
|
|
745
|
+
await this.createGroup();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return [];
|
|
821
749
|
}
|
|
822
|
-
async
|
|
750
|
+
async selectFresh() {
|
|
751
|
+
let entries = [];
|
|
752
|
+
try {
|
|
753
|
+
const res = await this.redis.xreadgroup(
|
|
754
|
+
"GROUP",
|
|
755
|
+
this.group,
|
|
756
|
+
this.consumer(),
|
|
757
|
+
"BLOCK",
|
|
758
|
+
Math.max(2, this.workerLoopIntervalMs | 0),
|
|
759
|
+
"COUNT",
|
|
760
|
+
this.workerBatchTasksCount,
|
|
761
|
+
"STREAMS",
|
|
762
|
+
this.stream,
|
|
763
|
+
">"
|
|
764
|
+
);
|
|
765
|
+
if (!res?.[0]?.[1]?.length) {
|
|
766
|
+
return [];
|
|
767
|
+
}
|
|
768
|
+
entries = res?.[0]?.[1] ?? [];
|
|
769
|
+
if (!entries?.length) {
|
|
770
|
+
return [];
|
|
771
|
+
}
|
|
772
|
+
} catch (err) {
|
|
773
|
+
if (String(err?.message || "").includes("NOGROUP")) {
|
|
774
|
+
await this.createGroup();
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return entries;
|
|
823
778
|
}
|
|
824
|
-
async
|
|
825
|
-
|
|
826
|
-
|
|
779
|
+
async waitAbortable(ttl) {
|
|
780
|
+
return new Promise((resolve) => {
|
|
781
|
+
const signal = this.signal();
|
|
782
|
+
if (signal?.aborted) {
|
|
783
|
+
return resolve();
|
|
784
|
+
}
|
|
785
|
+
const t = setTimeout(() => {
|
|
786
|
+
if (signal) {
|
|
787
|
+
signal.removeEventListener("abort", onAbort);
|
|
788
|
+
}
|
|
789
|
+
resolve();
|
|
790
|
+
}, ttl > 0 ? 25 + Math.floor(Math.random() * 50) : 5 + Math.floor(Math.random() * 15));
|
|
791
|
+
t.unref?.();
|
|
792
|
+
function onAbort() {
|
|
793
|
+
clearTimeout(t);
|
|
794
|
+
resolve();
|
|
795
|
+
}
|
|
796
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
797
|
+
});
|
|
827
798
|
}
|
|
828
|
-
|
|
799
|
+
heartbeat(keys) {
|
|
800
|
+
if (this.workerExecuteLockTimeoutMs <= 0) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
let timer, alive = true, hbFails = 0;
|
|
804
|
+
const workerHeartbeatTimeoutMs = Math.max(1e3, Math.floor(Math.max(5e3, this.workerExecuteLockTimeoutMs | 0) / 4));
|
|
805
|
+
const stop = () => {
|
|
806
|
+
alive = false;
|
|
807
|
+
if (timer) {
|
|
808
|
+
clearTimeout(timer);
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
const onAbort = () => stop();
|
|
829
812
|
const signal = this.signal();
|
|
830
|
-
|
|
813
|
+
signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
814
|
+
const tick = async () => {
|
|
815
|
+
if (!alive) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
831
818
|
try {
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
819
|
+
const r = await this.heartbeat(keys);
|
|
820
|
+
hbFails = r ? 0 : hbFails + 1;
|
|
821
|
+
if (hbFails >= 3) {
|
|
822
|
+
throw new Error("Heartbeat lost.");
|
|
836
823
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
840
|
-
|
|
824
|
+
} catch {
|
|
825
|
+
hbFails++;
|
|
826
|
+
if (hbFails >= 6) {
|
|
827
|
+
stop();
|
|
828
|
+
return;
|
|
841
829
|
}
|
|
842
|
-
} catch (err) {
|
|
843
|
-
await (0, import_full_utils.wait)(600);
|
|
844
830
|
}
|
|
831
|
+
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
832
|
+
};
|
|
833
|
+
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
834
|
+
return () => {
|
|
835
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
836
|
+
stop();
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
normalizeEntries(raw) {
|
|
840
|
+
if (!Array.isArray(raw)) {
|
|
841
|
+
return [];
|
|
845
842
|
}
|
|
843
|
+
return Array.from(raw || []).map((e) => {
|
|
844
|
+
const id = Buffer.isBuffer(e?.[0]) ? e[0].toString() : e?.[0];
|
|
845
|
+
const kvRaw = e?.[1] ?? [];
|
|
846
|
+
const kv = Array.isArray(kvRaw) ? kvRaw.map((x) => Buffer.isBuffer(x) ? x.toString() : x) : [];
|
|
847
|
+
return [id, kv];
|
|
848
|
+
}).filter(([id, kv]) => typeof id === "string" && id.length > 0 && Array.isArray(kv) && (kv.length & 1) === 0).map(([id, kv]) => {
|
|
849
|
+
const { idemKey = "", job, createdAt, payload } = this.values(kv);
|
|
850
|
+
return [id, this.payload(payload), createdAt, job, idemKey];
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
values(value) {
|
|
854
|
+
const result = {};
|
|
855
|
+
for (let i = 0; i < value.length; i += 2) {
|
|
856
|
+
result[value[i]] = value[i + 1];
|
|
857
|
+
}
|
|
858
|
+
return result;
|
|
859
|
+
}
|
|
860
|
+
payload(data) {
|
|
861
|
+
try {
|
|
862
|
+
return JSON.parse(data?.payload);
|
|
863
|
+
} catch (err) {
|
|
864
|
+
}
|
|
865
|
+
return data;
|
|
866
|
+
}
|
|
867
|
+
signal() {
|
|
868
|
+
return this.abort.signal;
|
|
869
|
+
}
|
|
870
|
+
consumer() {
|
|
871
|
+
return `${String(this.consumerHost || "host")}:${process.pid}`;
|
|
846
872
|
}
|
|
847
873
|
};
|
|
848
874
|
// Annotate the CommonJS export names for ESM import in node:
|