qdone 2.0.5-alpha → 2.0.7-alpha
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/commonjs/src/defaults.js +3 -1
- package/commonjs/src/enqueue.js +133 -56
- package/commonjs/src/exponentialBackoff.js +172 -0
- package/commonjs/src/idleQueues.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/defaults.js +3 -1
- package/src/enqueue.js +82 -23
- package/src/exponentialBackoff.js +111 -0
- package/src/idleQueues.js +1 -1
- package/src/worker.js +21 -6
package/commonjs/src/defaults.js
CHANGED
|
@@ -23,9 +23,10 @@ exports.defaults = Object.freeze({
|
|
|
23
23
|
// Enqueue
|
|
24
24
|
groupId: (0, uuid_1.v1)(),
|
|
25
25
|
groupIdPerMessage: false,
|
|
26
|
-
deduplicationId:
|
|
26
|
+
deduplicationId: undefined,
|
|
27
27
|
messageRetentionPeriod: 1209600,
|
|
28
28
|
delay: 0,
|
|
29
|
+
sendRetries: 6,
|
|
29
30
|
failDelay: 0,
|
|
30
31
|
dlq: false,
|
|
31
32
|
dlqSuffix: '_dead',
|
|
@@ -74,6 +75,7 @@ function getOptionsWithDefaults(options) {
|
|
|
74
75
|
deduplicationId: options.deduplicationId || options['deduplication-id'] || exports.defaults.deduplicationId,
|
|
75
76
|
messageRetentionPeriod: options.messageRetentionPeriod || options['message-retention-period'] || exports.defaults.messageRetentionPeriod,
|
|
76
77
|
delay: options.delay || exports.defaults.delay,
|
|
78
|
+
sendRetries: options['send-retries'] || exports.defaults.sendRetries,
|
|
77
79
|
failDelay: options.failDelay || options['fail-delay'] || exports.defaults.failDelay,
|
|
78
80
|
dlq: dlq || exports.defaults.dlq,
|
|
79
81
|
dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || exports.defaults.dlqSuffix,
|
package/commonjs/src/enqueue.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// const Q = require('q')
|
|
3
|
-
// const debug = require('debug')('qdone:enqueue')
|
|
4
|
-
// const chalk = require('chalk')
|
|
5
|
-
// const uuid = require('uuid')
|
|
6
|
-
// const qrlCache = require('./qrlCache')
|
|
7
|
-
// const AWS = require('aws-sdk')
|
|
8
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -46,6 +40,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
46
40
|
};
|
|
47
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
42
|
exports.enqueueBatch = exports.enqueue = exports.addMessage = exports.flushMessages = exports.sendMessageBatch = exports.sendMessage = exports.formatMessage = exports.getQueueAttributes = exports.getOrCreateQueue = exports.getOrCreateFailQueue = exports.getOrCreateDLQ = void 0;
|
|
43
|
+
var node_1 = require("@sentry/node");
|
|
49
44
|
var uuid_1 = require("uuid");
|
|
50
45
|
var chalk_1 = __importDefault(require("chalk"));
|
|
51
46
|
var debug_1 = __importDefault(require("debug"));
|
|
@@ -53,6 +48,7 @@ var client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
|
53
48
|
var qrlCache_js_1 = require("./qrlCache.js");
|
|
54
49
|
var sqs_js_1 = require("./sqs.js");
|
|
55
50
|
var defaults_js_1 = require("./defaults.js");
|
|
51
|
+
var exponentialBackoff_js_1 = require("./exponentialBackoff.js");
|
|
56
52
|
var debug = (0, debug_1.default)('qdone:enqueue');
|
|
57
53
|
function getOrCreateDLQ(queue, opt) {
|
|
58
54
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -258,29 +254,63 @@ function formatMessage(command, id) {
|
|
|
258
254
|
return message;
|
|
259
255
|
}
|
|
260
256
|
exports.formatMessage = formatMessage;
|
|
257
|
+
// Retry happens within the context of the send functions
|
|
258
|
+
var retryableExceptions = [
|
|
259
|
+
client_sqs_1.RequestThrottled,
|
|
260
|
+
client_sqs_1.KmsThrottled,
|
|
261
|
+
client_sqs_1.QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
|
|
262
|
+
];
|
|
261
263
|
function sendMessage(qrl, command, opt) {
|
|
262
264
|
return __awaiter(this, void 0, void 0, function () {
|
|
263
|
-
var params, client, cmd,
|
|
265
|
+
var params, client, cmd, backoff, send, shouldRetry, result;
|
|
266
|
+
var _this = this;
|
|
264
267
|
return __generator(this, function (_a) {
|
|
265
268
|
switch (_a.label) {
|
|
266
269
|
case 0:
|
|
267
270
|
debug('sendMessage(', qrl, command, ')');
|
|
268
271
|
params = Object.assign({ QueueUrl: qrl }, formatMessage(command));
|
|
269
|
-
// Add in group id if we're using fifo
|
|
270
272
|
if (opt.fifo) {
|
|
271
273
|
params.MessageGroupId = opt.groupId;
|
|
272
|
-
params.MessageDeduplicationId = opt.deduplicationId;
|
|
274
|
+
params.MessageDeduplicationId = opt.deduplicationId || (0, uuid_1.v1)();
|
|
273
275
|
}
|
|
274
276
|
if (opt.delay)
|
|
275
277
|
params.DelaySeconds = opt.delay;
|
|
276
278
|
client = (0, sqs_js_1.getSQSClient)();
|
|
277
279
|
cmd = new client_sqs_1.SendMessageCommand(params);
|
|
278
280
|
debug({ cmd: cmd });
|
|
279
|
-
|
|
281
|
+
backoff = new exponentialBackoff_js_1.ExponentialBackoff(opt.sendRetries);
|
|
282
|
+
send = function (attemptNumber) { return __awaiter(_this, void 0, void 0, function () {
|
|
283
|
+
var data;
|
|
284
|
+
return __generator(this, function (_a) {
|
|
285
|
+
switch (_a.label) {
|
|
286
|
+
case 0:
|
|
287
|
+
cmd.input.attemptNumber = attemptNumber;
|
|
288
|
+
return [4 /*yield*/, client.send(cmd)];
|
|
289
|
+
case 1:
|
|
290
|
+
data = _a.sent();
|
|
291
|
+
debug('sendMessage returned', data);
|
|
292
|
+
return [2 /*return*/, data];
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}); };
|
|
296
|
+
shouldRetry = function (result, error) { return __awaiter(_this, void 0, void 0, function () {
|
|
297
|
+
var _i, retryableExceptions_1, exceptionClass;
|
|
298
|
+
return __generator(this, function (_a) {
|
|
299
|
+
for (_i = 0, retryableExceptions_1 = retryableExceptions; _i < retryableExceptions_1.length; _i++) {
|
|
300
|
+
exceptionClass = retryableExceptions_1[_i];
|
|
301
|
+
if (error instanceof exceptionClass) {
|
|
302
|
+
debug({ sendMessageRetryingBecause: { error: error, result: result } });
|
|
303
|
+
return [2 /*return*/, true];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [2 /*return*/, false];
|
|
307
|
+
});
|
|
308
|
+
}); };
|
|
309
|
+
return [4 /*yield*/, backoff.run(send, shouldRetry)];
|
|
280
310
|
case 1:
|
|
281
|
-
|
|
282
|
-
debug(
|
|
283
|
-
return [2 /*return*/,
|
|
311
|
+
result = _a.sent();
|
|
312
|
+
debug({ sendMessageResult: result });
|
|
313
|
+
return [2 /*return*/, result];
|
|
284
314
|
}
|
|
285
315
|
});
|
|
286
316
|
});
|
|
@@ -288,34 +318,84 @@ function sendMessage(qrl, command, opt) {
|
|
|
288
318
|
exports.sendMessage = sendMessage;
|
|
289
319
|
function sendMessageBatch(qrl, messages, opt) {
|
|
290
320
|
return __awaiter(this, void 0, void 0, function () {
|
|
291
|
-
var params, uuidFunction, client, cmd,
|
|
321
|
+
var params, uuidFunction, client, cmd, backoff, send, shouldRetry;
|
|
322
|
+
var _this = this;
|
|
292
323
|
return __generator(this, function (_a) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
debug('sendMessageBatch(', qrl, messages.map(function (e) { return Object.assign(Object.assign({}, e), { MessageBody: e.MessageBody.slice(0, 10) + '...' }); }), ')');
|
|
325
|
+
params = { Entries: messages, QueueUrl: qrl };
|
|
326
|
+
uuidFunction = opt.uuidFunction || uuid_1.v1;
|
|
327
|
+
// Add in group id if we're using fifo
|
|
328
|
+
if (opt.fifo) {
|
|
329
|
+
params.Entries = params.Entries.map(function (message) { return Object.assign({
|
|
330
|
+
MessageGroupId: opt.groupIdPerMessage ? uuidFunction() : opt.groupId,
|
|
331
|
+
MessageDeduplicationId: uuidFunction()
|
|
332
|
+
}, message); });
|
|
333
|
+
}
|
|
334
|
+
if (opt.delay) {
|
|
335
|
+
params.Entries = params.Entries.map(function (message) {
|
|
336
|
+
return Object.assign({ DelaySeconds: opt.delay }, message);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (opt.sentryDsn) {
|
|
340
|
+
(0, node_1.addBreadcrumb)({ category: 'sendMessageBatch', message: JSON.stringify({ params: params }), level: 'debug' });
|
|
341
|
+
}
|
|
342
|
+
debug({ params: params });
|
|
343
|
+
client = (0, sqs_js_1.getSQSClient)();
|
|
344
|
+
cmd = new client_sqs_1.SendMessageBatchCommand(params);
|
|
345
|
+
debug({ cmd: cmd });
|
|
346
|
+
backoff = new exponentialBackoff_js_1.ExponentialBackoff(opt.sendRetries);
|
|
347
|
+
send = function (attemptNumber) { return __awaiter(_this, void 0, void 0, function () {
|
|
348
|
+
var data;
|
|
349
|
+
return __generator(this, function (_a) {
|
|
350
|
+
switch (_a.label) {
|
|
351
|
+
case 0:
|
|
352
|
+
debug({ sendMessageBatchSend: { attemptNumber: attemptNumber, params: params } });
|
|
353
|
+
return [4 /*yield*/, client.send(cmd)];
|
|
354
|
+
case 1:
|
|
355
|
+
data = _a.sent();
|
|
356
|
+
return [2 /*return*/, data];
|
|
304
357
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
358
|
+
});
|
|
359
|
+
}); };
|
|
360
|
+
shouldRetry = function (result, error) {
|
|
361
|
+
debug({ shouldRetry: { error: error, result: result } });
|
|
362
|
+
if (result) {
|
|
363
|
+
// Handle failed result of one or more messages in the batch
|
|
364
|
+
if (result.Failed && result.Failed.length) {
|
|
365
|
+
var _loop_1 = function (failed) {
|
|
366
|
+
// Find corresponding messages
|
|
367
|
+
var original = params.Entries.find(function (e) { return e.Id === failed.Id; });
|
|
368
|
+
var info = { failed: failed, original: original, opt: opt };
|
|
369
|
+
if (opt.sentryDsn) {
|
|
370
|
+
(0, node_1.addBreadcrumb)({ category: 'sendMessageBatch', message: 'Failed message: ' + JSON.stringify(info), level: 'error' });
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
console.error(info);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
for (var _i = 0, _a = result.Failed; _i < _a.length; _i++) {
|
|
377
|
+
var failed = _a[_i];
|
|
378
|
+
_loop_1(failed);
|
|
379
|
+
}
|
|
380
|
+
throw new Error('One or more message failures: ' + JSON.stringify(result.Failed));
|
|
309
381
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
382
|
+
}
|
|
383
|
+
if (error) {
|
|
384
|
+
// Handle a failed result from an overall error on request
|
|
385
|
+
if (opt.sentryDsn) {
|
|
386
|
+
(0, node_1.addBreadcrumb)({ category: 'sendMessageBatch', message: JSON.stringify({ error: error }), level: 'error' });
|
|
387
|
+
}
|
|
388
|
+
for (var _b = 0, retryableExceptions_2 = retryableExceptions; _b < retryableExceptions_2.length; _b++) {
|
|
389
|
+
var exceptionClass = retryableExceptions_2[_b];
|
|
390
|
+
debug({ exceptionClass: exceptionClass, retryableExceptions: retryableExceptions });
|
|
391
|
+
if (error instanceof exceptionClass) {
|
|
392
|
+
debug({ sendMessageRetryingBecause: { error: error, result: result } });
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
return [2 /*return*/, backoff.run(send, shouldRetry)];
|
|
319
399
|
});
|
|
320
400
|
});
|
|
321
401
|
}
|
|
@@ -396,6 +476,7 @@ function addMessage(qrl, command, opt) {
|
|
|
396
476
|
message = formatMessage(command, messageIndex++);
|
|
397
477
|
messages[qrl] = messages[qrl] || [];
|
|
398
478
|
messages[qrl].push(message);
|
|
479
|
+
debug({ location: 'addMessage', messages: messages });
|
|
399
480
|
if (messages[qrl].length >= 10) {
|
|
400
481
|
return [2 /*return*/, flushMessages(qrl, opt)];
|
|
401
482
|
}
|
|
@@ -431,9 +512,9 @@ exports.enqueue = enqueue;
|
|
|
431
512
|
//
|
|
432
513
|
function enqueueBatch(pairs, options) {
|
|
433
514
|
return __awaiter(this, void 0, void 0, function () {
|
|
434
|
-
var opt, normalizedPairs, uniqueQnames, createPromises, _i, uniqueQnames_1, qname,
|
|
435
|
-
return __generator(this, function (
|
|
436
|
-
switch (
|
|
515
|
+
var opt, normalizedPairs, uniqueQnames, createPromises, _i, uniqueQnames_1, qname, initialFlushTotal, _a, normalizedPairs_1, _b, qname, command, qrl, _c, extraFlushPromises, qrl, extraFlushCounts, extraFlushTotal, totalFlushed;
|
|
516
|
+
return __generator(this, function (_d) {
|
|
517
|
+
switch (_d.label) {
|
|
437
518
|
case 0:
|
|
438
519
|
debug('enqueueBatch(', pairs, ')');
|
|
439
520
|
opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
|
|
@@ -455,39 +536,35 @@ function enqueueBatch(pairs, options) {
|
|
|
455
536
|
// so go back through the list of pairs and fire off messages
|
|
456
537
|
];
|
|
457
538
|
case 1:
|
|
458
|
-
|
|
539
|
+
_d.sent();
|
|
459
540
|
// After we've prefetched, all qrls are in cache
|
|
460
541
|
// so go back through the list of pairs and fire off messages
|
|
461
542
|
requestCount = 0;
|
|
462
|
-
|
|
543
|
+
initialFlushTotal = 0;
|
|
463
544
|
_a = 0, normalizedPairs_1 = normalizedPairs;
|
|
464
|
-
|
|
545
|
+
_d.label = 2;
|
|
465
546
|
case 2:
|
|
466
|
-
if (!(_a < normalizedPairs_1.length)) return [3 /*break*/,
|
|
547
|
+
if (!(_a < normalizedPairs_1.length)) return [3 /*break*/, 6];
|
|
467
548
|
_b = normalizedPairs_1[_a], qname = _b.qname, command = _b.command;
|
|
468
549
|
return [4 /*yield*/, getOrCreateQueue(qname, opt)];
|
|
469
550
|
case 3:
|
|
470
|
-
qrl =
|
|
471
|
-
|
|
472
|
-
|
|
551
|
+
qrl = _d.sent();
|
|
552
|
+
_c = initialFlushTotal;
|
|
553
|
+
return [4 /*yield*/, addMessage(qrl, command, opt)];
|
|
473
554
|
case 4:
|
|
555
|
+
initialFlushTotal = _c + _d.sent();
|
|
556
|
+
_d.label = 5;
|
|
557
|
+
case 5:
|
|
474
558
|
_a++;
|
|
475
559
|
return [3 /*break*/, 2];
|
|
476
|
-
case 5: return [4 /*yield*/, Promise.all(addMessagePromises)
|
|
477
|
-
// Count up how many were flushed during add
|
|
478
|
-
];
|
|
479
560
|
case 6:
|
|
480
|
-
flushCounts = _c.sent();
|
|
481
|
-
// Count up how many were flushed during add
|
|
482
|
-
debug('flushCounts', flushCounts);
|
|
483
|
-
initialFlushTotal = flushCounts.reduce(function (a, b) { return a + b; }, 0);
|
|
484
561
|
extraFlushPromises = [];
|
|
485
562
|
for (qrl in messages) {
|
|
486
563
|
extraFlushPromises.push(flushMessages(qrl, opt));
|
|
487
564
|
}
|
|
488
565
|
return [4 /*yield*/, Promise.all(extraFlushPromises)];
|
|
489
566
|
case 7:
|
|
490
|
-
extraFlushCounts =
|
|
567
|
+
extraFlushCounts = _d.sent();
|
|
491
568
|
extraFlushTotal = extraFlushCounts.reduce(function (a, b) { return a + b; }, 0);
|
|
492
569
|
totalFlushed = initialFlushTotal + extraFlushTotal;
|
|
493
570
|
debug({ initialFlushTotal: initialFlushTotal, extraFlushTotal: extraFlushTotal, totalFlushed: totalFlushed });
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Exponential backoff controller.
|
|
4
|
+
* usage:
|
|
5
|
+
* const exp = new ExponentialBackoff()
|
|
6
|
+
* const result = await exp.run(
|
|
7
|
+
* function action (attemptNumber) {
|
|
8
|
+
* console.log(attemptNumber) // 1, 2, 3, ...
|
|
9
|
+
* return axios.post(...)
|
|
10
|
+
* },
|
|
11
|
+
* function shouldRetry (returnValue, error) {
|
|
12
|
+
* if (returnValue && return value.code = 500) return true
|
|
13
|
+
* if (error && error.message === 'Internal Server Error') return true
|
|
14
|
+
* }
|
|
15
|
+
* )
|
|
16
|
+
*/
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
28
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
+
function step(op) {
|
|
31
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
+
switch (op[0]) {
|
|
36
|
+
case 0: case 1: t = op; break;
|
|
37
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
+
default:
|
|
41
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
+
if (t[2]) _.ops.pop();
|
|
46
|
+
_.trys.pop(); continue;
|
|
47
|
+
}
|
|
48
|
+
op = body.call(thisArg, _);
|
|
49
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.ExponentialBackoff = void 0;
|
|
55
|
+
var ExponentialBackoff = /** @class */ (function () {
|
|
56
|
+
/**
|
|
57
|
+
* Creates various behaviors for backoff.
|
|
58
|
+
* @param {number} maxRetries - Number of times to attempt the action before
|
|
59
|
+
* throwing an error. Defaults to 3.
|
|
60
|
+
* @param {number} maxJitterPercent - Jitter as a percentage of the delay.
|
|
61
|
+
* For example, if the exponential delay is 2 seconds, then a jitter of
|
|
62
|
+
* 0.5 could lead to a delay as low as 1 second and as high as 3 seconds,
|
|
63
|
+
* since 0.5 * 2 = 1. Defaults to 0.5.
|
|
64
|
+
* @param {number} exponentBase - The base for the exponent. Defaults to 2,
|
|
65
|
+
* which means the delay doubles every attempt.
|
|
66
|
+
*/
|
|
67
|
+
function ExponentialBackoff(maxRetries, maxJitterPercent, exponentBase) {
|
|
68
|
+
if (maxRetries === void 0) { maxRetries = 3; }
|
|
69
|
+
if (maxJitterPercent === void 0) { maxJitterPercent = 0.5; }
|
|
70
|
+
if (exponentBase === void 0) { exponentBase = 2; }
|
|
71
|
+
if (maxRetries < 1)
|
|
72
|
+
throw new Error('maxRetries must be >= 1');
|
|
73
|
+
if (maxJitterPercent < 0.1 || maxJitterPercent > 1)
|
|
74
|
+
throw new Error('maxJitterPercent must be in the interval [0.1, 1]');
|
|
75
|
+
if (exponentBase < 1 || exponentBase > 10)
|
|
76
|
+
throw new Error('exponentBase must be in the range [1, 10]');
|
|
77
|
+
this.maxRetries = parseInt(maxRetries);
|
|
78
|
+
this.maxJitterPercent = parseFloat(maxJitterPercent);
|
|
79
|
+
this.exponentBase = parseFloat(exponentBase);
|
|
80
|
+
this.attemptNumber = 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Calculates how many ms to delay based on the current attempt number.
|
|
84
|
+
*/
|
|
85
|
+
ExponentialBackoff.prototype.calculateDelayMs = function (attemptNumber) {
|
|
86
|
+
var secondsRaw = Math.pow(this.exponentBase, attemptNumber); // 2, 4, 8, 16, ....
|
|
87
|
+
var jitter = this.maxJitterPercent * (Math.random() - 0.5); // [-0.5, 0.5]
|
|
88
|
+
var delayMs = Math.round(secondsRaw * (1 + jitter) * 1000);
|
|
89
|
+
// console.log({ secondsRaw, jitter, delayMs })
|
|
90
|
+
return delayMs;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Resolves after a delay set by the current attempt.
|
|
94
|
+
*/
|
|
95
|
+
ExponentialBackoff.prototype.delay = function (attemptNumber) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
97
|
+
var delay;
|
|
98
|
+
return __generator(this, function (_a) {
|
|
99
|
+
delay = this.calculateDelayMs(attemptNumber);
|
|
100
|
+
// console.log({ function: 'delay', attemptNumber, delay })
|
|
101
|
+
return [2 /*return*/, new Promise(function (resolve, reject) { return setTimeout(resolve, delay); })];
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Call another function repeatedly, retrying with exponential backoff and
|
|
107
|
+
* jitter if not successful.
|
|
108
|
+
* @param {ExponentialBackoff~action} action - Callback that does the action
|
|
109
|
+
* to be attempted (web request, rpc, database call, etc). Will be called
|
|
110
|
+
* again after the exponential dealy if shouldRetry() returns true.
|
|
111
|
+
* @param {ExponentialBackoff~shouldRetry} shouldRetry - Callback that gets
|
|
112
|
+
* to look at the return value of action() and any potential exception. If
|
|
113
|
+
* this returns true then the action will be retried with the appropriate
|
|
114
|
+
* backoff delay. Defaults to a function that returns true if an exception
|
|
115
|
+
* is thrown.
|
|
116
|
+
*/
|
|
117
|
+
ExponentialBackoff.prototype.run = function (action, shouldRetry) {
|
|
118
|
+
var _this = this;
|
|
119
|
+
if (action === void 0) { action = function (attemptNumber) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
120
|
+
return [2 /*return*/, undefined];
|
|
121
|
+
}); }); }; }
|
|
122
|
+
if (shouldRetry === void 0) { shouldRetry = function (returnValue, error) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
123
|
+
return [2 /*return*/, !!error];
|
|
124
|
+
}); }); }; }
|
|
125
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
126
|
+
var attemptNumber, result, e_1;
|
|
127
|
+
return __generator(this, function (_a) {
|
|
128
|
+
switch (_a.label) {
|
|
129
|
+
case 0:
|
|
130
|
+
attemptNumber = 0;
|
|
131
|
+
_a.label = 1;
|
|
132
|
+
case 1:
|
|
133
|
+
if (!(attemptNumber++ < this.maxRetries)) return [3 /*break*/, 14];
|
|
134
|
+
_a.label = 2;
|
|
135
|
+
case 2:
|
|
136
|
+
_a.trys.push([2, 8, , 13]);
|
|
137
|
+
return [4 /*yield*/, action(attemptNumber)];
|
|
138
|
+
case 3:
|
|
139
|
+
result = _a.sent();
|
|
140
|
+
return [4 /*yield*/, shouldRetry(result, undefined)];
|
|
141
|
+
case 4:
|
|
142
|
+
if (!_a.sent()) return [3 /*break*/, 6];
|
|
143
|
+
if (attemptNumber >= this.maxRetries)
|
|
144
|
+
throw new Error('Maximum number of attempts reached');
|
|
145
|
+
return [4 /*yield*/, this.delay(attemptNumber)];
|
|
146
|
+
case 5:
|
|
147
|
+
_a.sent();
|
|
148
|
+
return [3 /*break*/, 7];
|
|
149
|
+
case 6: return [2 /*return*/, result];
|
|
150
|
+
case 7: return [3 /*break*/, 13];
|
|
151
|
+
case 8:
|
|
152
|
+
e_1 = _a.sent();
|
|
153
|
+
return [4 /*yield*/, shouldRetry(undefined, e_1)];
|
|
154
|
+
case 9:
|
|
155
|
+
if (!_a.sent()) return [3 /*break*/, 11];
|
|
156
|
+
if (attemptNumber >= this.maxRetries)
|
|
157
|
+
throw e_1;
|
|
158
|
+
return [4 /*yield*/, this.delay(attemptNumber)];
|
|
159
|
+
case 10:
|
|
160
|
+
_a.sent();
|
|
161
|
+
return [3 /*break*/, 12];
|
|
162
|
+
case 11: throw e_1;
|
|
163
|
+
case 12: return [3 /*break*/, 13];
|
|
164
|
+
case 13: return [3 /*break*/, 1];
|
|
165
|
+
case 14: return [2 /*return*/];
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
return ExponentialBackoff;
|
|
171
|
+
}());
|
|
172
|
+
exports.ExponentialBackoff = ExponentialBackoff;
|
|
@@ -328,7 +328,7 @@ function processQueuePair(qname, qrl, opt) {
|
|
|
328
328
|
isFifo = qname.endsWith('.fifo');
|
|
329
329
|
normalizeOptions = Object.assign({}, opt, { fifo: isFifo });
|
|
330
330
|
fqname = (0, qrlCache_js_1.normalizeFailQueueName)(qname, normalizeOptions);
|
|
331
|
-
fqrl = (0, qrlCache_js_1.normalizeFailQueueName)(
|
|
331
|
+
fqrl = (0, qrlCache_js_1.normalizeFailQueueName)(fqname, normalizeOptions);
|
|
332
332
|
return [4 /*yield*/, checkIdle(qname, qrl, opt)];
|
|
333
333
|
case 1:
|
|
334
334
|
result = _b.sent();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qdone",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7-alpha",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "qdone",
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.7-alpha",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@aws-sdk/client-cloudwatch": "^3.465.0",
|
package/package.json
CHANGED
package/src/defaults.js
CHANGED
|
@@ -22,9 +22,10 @@ export const defaults = Object.freeze({
|
|
|
22
22
|
// Enqueue
|
|
23
23
|
groupId: uuidv1(),
|
|
24
24
|
groupIdPerMessage: false,
|
|
25
|
-
deduplicationId:
|
|
25
|
+
deduplicationId: undefined,
|
|
26
26
|
messageRetentionPeriod: 1209600,
|
|
27
27
|
delay: 0,
|
|
28
|
+
sendRetries: 6,
|
|
28
29
|
failDelay: 0,
|
|
29
30
|
dlq: false,
|
|
30
31
|
dlqSuffix: '_dead',
|
|
@@ -79,6 +80,7 @@ export function getOptionsWithDefaults (options) {
|
|
|
79
80
|
deduplicationId: options.deduplicationId || options['deduplication-id'] || defaults.deduplicationId,
|
|
80
81
|
messageRetentionPeriod: options.messageRetentionPeriod || options['message-retention-period'] || defaults.messageRetentionPeriod,
|
|
81
82
|
delay: options.delay || defaults.delay,
|
|
83
|
+
sendRetries: options['send-retries'] || defaults.sendRetries,
|
|
82
84
|
failDelay: options.failDelay || options['fail-delay'] || defaults.failDelay,
|
|
83
85
|
dlq: dlq || defaults.dlq,
|
|
84
86
|
dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || defaults.dlqSuffix,
|
package/src/enqueue.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// const debug = require('debug')('qdone:enqueue')
|
|
3
|
-
// const chalk = require('chalk')
|
|
4
|
-
// const uuid = require('uuid')
|
|
5
|
-
// const qrlCache = require('./qrlCache')
|
|
6
|
-
// const AWS = require('aws-sdk')
|
|
7
|
-
|
|
1
|
+
import { addBreadcrumb } from '@sentry/node'
|
|
8
2
|
import { v1 as uuidV1 } from 'uuid'
|
|
9
3
|
import chalk from 'chalk'
|
|
10
4
|
import Debug from 'debug'
|
|
@@ -13,7 +7,9 @@ import {
|
|
|
13
7
|
GetQueueAttributesCommand,
|
|
14
8
|
SendMessageCommand,
|
|
15
9
|
SendMessageBatchCommand,
|
|
16
|
-
QueueDoesNotExist
|
|
10
|
+
QueueDoesNotExist,
|
|
11
|
+
RequestThrottled,
|
|
12
|
+
KmsThrottled
|
|
17
13
|
} from '@aws-sdk/client-sqs'
|
|
18
14
|
|
|
19
15
|
import {
|
|
@@ -25,6 +21,7 @@ import {
|
|
|
25
21
|
} from './qrlCache.js'
|
|
26
22
|
import { getSQSClient } from './sqs.js'
|
|
27
23
|
import { getOptionsWithDefaults } from './defaults.js'
|
|
24
|
+
import { ExponentialBackoff } from './exponentialBackoff.js'
|
|
28
25
|
|
|
29
26
|
const debug = Debug('qdone:enqueue')
|
|
30
27
|
|
|
@@ -162,21 +159,45 @@ export function formatMessage (command, id) {
|
|
|
162
159
|
return message
|
|
163
160
|
}
|
|
164
161
|
|
|
162
|
+
// Retry happens within the context of the send functions
|
|
163
|
+
const retryableExceptions = [
|
|
164
|
+
RequestThrottled,
|
|
165
|
+
KmsThrottled,
|
|
166
|
+
QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
|
|
167
|
+
]
|
|
168
|
+
|
|
165
169
|
export async function sendMessage (qrl, command, opt) {
|
|
166
170
|
debug('sendMessage(', qrl, command, ')')
|
|
167
171
|
const params = Object.assign({ QueueUrl: qrl }, formatMessage(command))
|
|
168
|
-
// Add in group id if we're using fifo
|
|
169
172
|
if (opt.fifo) {
|
|
170
173
|
params.MessageGroupId = opt.groupId
|
|
171
|
-
params.MessageDeduplicationId = opt.deduplicationId
|
|
174
|
+
params.MessageDeduplicationId = opt.deduplicationId || uuidV1()
|
|
172
175
|
}
|
|
173
176
|
if (opt.delay) params.DelaySeconds = opt.delay
|
|
177
|
+
|
|
178
|
+
// Send it
|
|
174
179
|
const client = getSQSClient()
|
|
175
180
|
const cmd = new SendMessageCommand(params)
|
|
176
181
|
debug({ cmd })
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
const backoff = new ExponentialBackoff(opt.sendRetries)
|
|
183
|
+
const send = async (attemptNumber) => {
|
|
184
|
+
cmd.input.attemptNumber = attemptNumber
|
|
185
|
+
const data = await client.send(cmd)
|
|
186
|
+
debug('sendMessage returned', data)
|
|
187
|
+
return data
|
|
188
|
+
}
|
|
189
|
+
const shouldRetry = async (result, error) => {
|
|
190
|
+
for (const exceptionClass of retryableExceptions) {
|
|
191
|
+
if (error instanceof exceptionClass) {
|
|
192
|
+
debug({ sendMessageRetryingBecause: { error, result } })
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
const result = await backoff.run(send, shouldRetry)
|
|
199
|
+
debug({ sendMessageResult: result })
|
|
200
|
+
return result
|
|
180
201
|
}
|
|
181
202
|
|
|
182
203
|
export async function sendMessageBatch (qrl, messages, opt) {
|
|
@@ -196,12 +217,54 @@ export async function sendMessageBatch (qrl, messages, opt) {
|
|
|
196
217
|
params.Entries = params.Entries.map(message =>
|
|
197
218
|
Object.assign({ DelaySeconds: opt.delay }, message))
|
|
198
219
|
}
|
|
220
|
+
if (opt.sentryDsn) {
|
|
221
|
+
addBreadcrumb({ category: 'sendMessageBatch', message: JSON.stringify({ params }), level: 'debug' })
|
|
222
|
+
}
|
|
223
|
+
debug({ params })
|
|
224
|
+
|
|
225
|
+
// Send them
|
|
199
226
|
const client = getSQSClient()
|
|
200
227
|
const cmd = new SendMessageBatchCommand(params)
|
|
201
228
|
debug({ cmd })
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
229
|
+
const backoff = new ExponentialBackoff(opt.sendRetries)
|
|
230
|
+
const send = async (attemptNumber) => {
|
|
231
|
+
debug({ sendMessageBatchSend: { attemptNumber, params } })
|
|
232
|
+
const data = await client.send(cmd)
|
|
233
|
+
return data
|
|
234
|
+
}
|
|
235
|
+
const shouldRetry = (result, error) => {
|
|
236
|
+
debug({ shouldRetry: { error, result } })
|
|
237
|
+
if (result) {
|
|
238
|
+
// Handle failed result of one or more messages in the batch
|
|
239
|
+
if (result.Failed && result.Failed.length) {
|
|
240
|
+
for (const failed of result.Failed) {
|
|
241
|
+
// Find corresponding messages
|
|
242
|
+
const original = params.Entries.find((e) => e.Id === failed.Id)
|
|
243
|
+
const info = { failed, original, opt }
|
|
244
|
+
if (opt.sentryDsn) {
|
|
245
|
+
addBreadcrumb({ category: 'sendMessageBatch', message: 'Failed message: ' + JSON.stringify(info), level: 'error' })
|
|
246
|
+
} else {
|
|
247
|
+
console.error(info)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
throw new Error('One or more message failures: ' + JSON.stringify(result.Failed))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (error) {
|
|
254
|
+
// Handle a failed result from an overall error on request
|
|
255
|
+
if (opt.sentryDsn) {
|
|
256
|
+
addBreadcrumb({ category: 'sendMessageBatch', message: JSON.stringify({ error }), level: 'error' })
|
|
257
|
+
}
|
|
258
|
+
for (const exceptionClass of retryableExceptions) {
|
|
259
|
+
debug({ exceptionClass, retryableExceptions })
|
|
260
|
+
if (error instanceof exceptionClass) {
|
|
261
|
+
debug({ sendMessageRetryingBecause: { error, result } })
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return backoff.run(send, shouldRetry)
|
|
205
268
|
}
|
|
206
269
|
|
|
207
270
|
const messages = {}
|
|
@@ -263,6 +326,7 @@ export async function addMessage (qrl, command, opt) {
|
|
|
263
326
|
const message = formatMessage(command, messageIndex++)
|
|
264
327
|
messages[qrl] = messages[qrl] || []
|
|
265
328
|
messages[qrl].push(message)
|
|
329
|
+
debug({ location: 'addMessage', messages })
|
|
266
330
|
if (messages[qrl].length >= 10) {
|
|
267
331
|
return flushMessages(qrl, opt)
|
|
268
332
|
}
|
|
@@ -306,16 +370,11 @@ export async function enqueueBatch (pairs, options) {
|
|
|
306
370
|
// After we've prefetched, all qrls are in cache
|
|
307
371
|
// so go back through the list of pairs and fire off messages
|
|
308
372
|
requestCount = 0
|
|
309
|
-
|
|
373
|
+
let initialFlushTotal = 0
|
|
310
374
|
for (const { qname, command } of normalizedPairs) {
|
|
311
375
|
const qrl = await getOrCreateQueue(qname, opt)
|
|
312
|
-
|
|
376
|
+
initialFlushTotal += await addMessage(qrl, command, opt)
|
|
313
377
|
}
|
|
314
|
-
const flushCounts = await Promise.all(addMessagePromises)
|
|
315
|
-
|
|
316
|
-
// Count up how many were flushed during add
|
|
317
|
-
debug('flushCounts', flushCounts)
|
|
318
|
-
const initialFlushTotal = flushCounts.reduce((a, b) => a + b, 0)
|
|
319
378
|
|
|
320
379
|
// And flush any remaining messages
|
|
321
380
|
const extraFlushPromises = []
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exponential backoff controller.
|
|
3
|
+
* usage:
|
|
4
|
+
* const exp = new ExponentialBackoff()
|
|
5
|
+
* const result = await exp.run(
|
|
6
|
+
* function action (attemptNumber) {
|
|
7
|
+
* console.log(attemptNumber) // 1, 2, 3, ...
|
|
8
|
+
* return axios.post(...)
|
|
9
|
+
* },
|
|
10
|
+
* function shouldRetry (returnValue, error) {
|
|
11
|
+
* if (returnValue && return value.code = 500) return true
|
|
12
|
+
* if (error && error.message === 'Internal Server Error') return true
|
|
13
|
+
* }
|
|
14
|
+
* )
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class ExponentialBackoff {
|
|
18
|
+
/**
|
|
19
|
+
* Creates various behaviors for backoff.
|
|
20
|
+
* @param {number} maxRetries - Number of times to attempt the action before
|
|
21
|
+
* throwing an error. Defaults to 3.
|
|
22
|
+
* @param {number} maxJitterPercent - Jitter as a percentage of the delay.
|
|
23
|
+
* For example, if the exponential delay is 2 seconds, then a jitter of
|
|
24
|
+
* 0.5 could lead to a delay as low as 1 second and as high as 3 seconds,
|
|
25
|
+
* since 0.5 * 2 = 1. Defaults to 0.5.
|
|
26
|
+
* @param {number} exponentBase - The base for the exponent. Defaults to 2,
|
|
27
|
+
* which means the delay doubles every attempt.
|
|
28
|
+
*/
|
|
29
|
+
constructor (maxRetries = 3, maxJitterPercent = 0.5, exponentBase = 2) {
|
|
30
|
+
if (maxRetries < 1) throw new Error('maxRetries must be >= 1')
|
|
31
|
+
if (maxJitterPercent < 0.1 || maxJitterPercent > 1) throw new Error('maxJitterPercent must be in the interval [0.1, 1]')
|
|
32
|
+
if (exponentBase < 1 || exponentBase > 10) throw new Error('exponentBase must be in the range [1, 10]')
|
|
33
|
+
this.maxRetries = parseInt(maxRetries)
|
|
34
|
+
this.maxJitterPercent = parseFloat(maxJitterPercent)
|
|
35
|
+
this.exponentBase = parseFloat(exponentBase)
|
|
36
|
+
this.attemptNumber = 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculates how many ms to delay based on the current attempt number.
|
|
41
|
+
*/
|
|
42
|
+
calculateDelayMs (attemptNumber) {
|
|
43
|
+
const secondsRaw = this.exponentBase ** attemptNumber // 2, 4, 8, 16, ....
|
|
44
|
+
const jitter = this.maxJitterPercent * (Math.random() - 0.5) // [-0.5, 0.5]
|
|
45
|
+
const delayMs = Math.round(secondsRaw * (1 + jitter) * 1000)
|
|
46
|
+
// console.log({ secondsRaw, jitter, delayMs })
|
|
47
|
+
return delayMs
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves after a delay set by the current attempt.
|
|
52
|
+
*/
|
|
53
|
+
async delay (attemptNumber) {
|
|
54
|
+
// console.log(attemptNumber)
|
|
55
|
+
const delay = this.calculateDelayMs(attemptNumber)
|
|
56
|
+
// console.log({ function: 'delay', attemptNumber, delay })
|
|
57
|
+
return new Promise((resolve, reject) => setTimeout(resolve, delay))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Call another function repeatedly, retrying with exponential backoff and
|
|
62
|
+
* jitter if not successful.
|
|
63
|
+
* @param {ExponentialBackoff~action} action - Callback that does the action
|
|
64
|
+
* to be attempted (web request, rpc, database call, etc). Will be called
|
|
65
|
+
* again after the exponential dealy if shouldRetry() returns true.
|
|
66
|
+
* @param {ExponentialBackoff~shouldRetry} shouldRetry - Callback that gets
|
|
67
|
+
* to look at the return value of action() and any potential exception. If
|
|
68
|
+
* this returns true then the action will be retried with the appropriate
|
|
69
|
+
* backoff delay. Defaults to a function that returns true if an exception
|
|
70
|
+
* is thrown.
|
|
71
|
+
*/
|
|
72
|
+
async run (
|
|
73
|
+
action = async (attemptNumber) => undefined,
|
|
74
|
+
shouldRetry = async (returnValue, error) => !!error
|
|
75
|
+
) {
|
|
76
|
+
let attemptNumber = 0
|
|
77
|
+
while (attemptNumber++ < this.maxRetries) {
|
|
78
|
+
try {
|
|
79
|
+
const result = await action(attemptNumber)
|
|
80
|
+
if (await shouldRetry(result, undefined)) {
|
|
81
|
+
if (attemptNumber >= this.maxRetries) throw new Error('Maximum number of attempts reached')
|
|
82
|
+
await this.delay(attemptNumber)
|
|
83
|
+
} else {
|
|
84
|
+
return result
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
if (await shouldRetry(undefined, e)) {
|
|
88
|
+
if (attemptNumber >= this.maxRetries) throw e
|
|
89
|
+
await this.delay(attemptNumber)
|
|
90
|
+
} else {
|
|
91
|
+
throw e
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Callback used by run().
|
|
99
|
+
* @callback ExponentialBackoff~action
|
|
100
|
+
* @param {number} attemptNumber - Which attempt this is, i.e. 1, 2, 3, ...
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Callback used by run().
|
|
105
|
+
* @callback ExponentialBackoff~shouldRetry
|
|
106
|
+
* @param returnValue - The value returned by your action. If an exception
|
|
107
|
+
* was thrown by the action then this is undefined.
|
|
108
|
+
* @param error - The exception thrown by your action. If there was no
|
|
109
|
+
* exception, this is undefined.
|
|
110
|
+
*/
|
|
111
|
+
}
|
package/src/idleQueues.js
CHANGED
|
@@ -208,7 +208,7 @@ export async function processQueuePair (qname, qrl, opt) {
|
|
|
208
208
|
const normalizeOptions = Object.assign({}, opt, { fifo: isFifo })
|
|
209
209
|
// Generate fail queue name/url
|
|
210
210
|
const fqname = normalizeFailQueueName(qname, normalizeOptions)
|
|
211
|
-
const fqrl = normalizeFailQueueName(
|
|
211
|
+
const fqrl = normalizeFailQueueName(fqname, normalizeOptions)
|
|
212
212
|
|
|
213
213
|
// Idle check
|
|
214
214
|
const result = await checkIdle(qname, qrl, opt)
|
package/src/worker.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import {
|
|
6
6
|
ChangeMessageVisibilityCommand,
|
|
7
7
|
ReceiveMessageCommand,
|
|
8
|
-
DeleteMessageCommand
|
|
8
|
+
DeleteMessageCommand,
|
|
9
|
+
QueueDoesNotExist
|
|
9
10
|
} from '@aws-sdk/client-sqs'
|
|
10
11
|
import { exec } from 'child_process' // node:child_process
|
|
11
12
|
import treeKill from 'tree-kill'
|
|
@@ -208,11 +209,25 @@ export async function listen (queues, options) {
|
|
|
208
209
|
chalk.blue(' (' + qrl + ')')
|
|
209
210
|
)
|
|
210
211
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
try {
|
|
213
|
+
// Aggregate the results
|
|
214
|
+
const { noJobs, jobsSucceeded, jobsFailed } = await pollForJobs(qname, qrl, opt)
|
|
215
|
+
stats.noJobs += noJobs
|
|
216
|
+
stats.jobsFailed += jobsFailed
|
|
217
|
+
stats.jobsSucceeded += jobsSucceeded
|
|
218
|
+
} catch (e) {
|
|
219
|
+
if (e instanceof QueueDoesNotExist) {
|
|
220
|
+
if (opt.verbose) {
|
|
221
|
+
console.error(
|
|
222
|
+
chalk.yellow('Warning: Queue ') +
|
|
223
|
+
qname.slice(opt.prefix.length) +
|
|
224
|
+
chalk.yellow(' does not exist.')
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
throw e
|
|
229
|
+
}
|
|
230
|
+
}
|
|
216
231
|
}
|
|
217
232
|
return stats
|
|
218
233
|
}
|