qdone 1.7.0 → 2.0.0-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/README.md +9 -1
- package/commonjs/index.js +10 -0
- package/commonjs/package.json +3 -0
- package/commonjs/src/cache.js +142 -0
- package/commonjs/src/cloudWatch.js +148 -0
- package/commonjs/src/consumer.js +483 -0
- package/commonjs/src/defaults.js +107 -0
- package/commonjs/src/enqueue.js +498 -0
- package/commonjs/src/idleQueues.js +466 -0
- package/commonjs/src/qrlCache.js +250 -0
- package/commonjs/src/sqs.js +160 -0
- package/npm-shrinkwrap.json +17240 -3367
- package/package.json +41 -29
- package/src/bin.js +3 -0
- package/src/cache.js +18 -22
- package/src/cli.js +268 -182
- package/src/cloudWatch.js +97 -0
- package/src/consumer.js +346 -0
- package/src/defaults.js +114 -0
- package/src/enqueue.js +239 -196
- package/src/idleQueues.js +242 -223
- package/src/monitor.js +53 -0
- package/src/qrlCache.js +110 -83
- package/src/sentry.js +30 -0
- package/src/sqs.js +73 -0
- package/src/worker.js +197 -202
- package/.coveralls.yml +0 -1
- package/.travis.yml +0 -19
- package/CHANGELOG.md +0 -121
- package/index.js +0 -6
- package/qdone +0 -2
- package/test/fixtures/test-child-kill-linux.sh +0 -9
- package/test/fixtures/test-fifo01-x24.batch +0 -24
- package/test/fixtures/test-too-big-1.batch +0 -10
- package/test/fixtures/test-unique01-x24.batch +0 -24
- package/test/fixtures/test-unique02-x24.batch +0 -24
- package/test/fixtures/test-unique24-x24.batch +0 -24
- package/test/fixtures/test-unique24-x240.batch +0 -240
- package/test/test.cache.js +0 -61
- package/test/test.cli.js +0 -1609
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Consumer implementation.
|
|
4
|
+
*/
|
|
5
|
+
var __extends = (this && this.__extends) || (function () {
|
|
6
|
+
var extendStatics = function (d, b) {
|
|
7
|
+
extendStatics = Object.setPrototypeOf ||
|
|
8
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
9
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
10
|
+
return extendStatics(d, b);
|
|
11
|
+
};
|
|
12
|
+
return function (d, b) {
|
|
13
|
+
if (typeof b !== "function" && b !== null)
|
|
14
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
15
|
+
extendStatics(d, b);
|
|
16
|
+
function __() { this.constructor = d; }
|
|
17
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
18
|
+
};
|
|
19
|
+
})();
|
|
20
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
21
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
22
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
23
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
24
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
25
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
26
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
30
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
31
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
32
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
33
|
+
function step(op) {
|
|
34
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
35
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
36
|
+
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;
|
|
37
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
38
|
+
switch (op[0]) {
|
|
39
|
+
case 0: case 1: t = op; break;
|
|
40
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
41
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
42
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
43
|
+
default:
|
|
44
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
45
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
46
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
47
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
48
|
+
if (t[2]) _.ops.pop();
|
|
49
|
+
_.trys.pop(); continue;
|
|
50
|
+
}
|
|
51
|
+
op = body.call(thisArg, _);
|
|
52
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
53
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
57
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
58
|
+
};
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.processMessages = exports.resolveQueues = exports.pollSingleQueue = exports.processMessage = exports.requestShutdown = exports.DoNotProcess = void 0;
|
|
61
|
+
var client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
62
|
+
var chalk_1 = __importDefault(require("chalk"));
|
|
63
|
+
var debug_1 = __importDefault(require("debug"));
|
|
64
|
+
var qrlCache_js_1 = require("./qrlCache.js");
|
|
65
|
+
var idleQueues_js_1 = require("./idleQueues.js");
|
|
66
|
+
var defaults_js_1 = require("./defaults.js");
|
|
67
|
+
var sqs_js_1 = require("./sqs.js");
|
|
68
|
+
//
|
|
69
|
+
// Throwing an instance of this Error allows the processMessages callback to
|
|
70
|
+
// refuse a message which then gets immediately returned to the queue.
|
|
71
|
+
//
|
|
72
|
+
// This has the side effect of throtting the queue since it stops polling on
|
|
73
|
+
// the queue until the next queue resolution in processMessages.
|
|
74
|
+
//
|
|
75
|
+
// This is useful for implementing schedulers on top of qdone, for example, to
|
|
76
|
+
// look at the queue name and decide whether to take on a new message.
|
|
77
|
+
//
|
|
78
|
+
var DoNotProcess = /** @class */ (function (_super) {
|
|
79
|
+
__extends(DoNotProcess, _super);
|
|
80
|
+
function DoNotProcess() {
|
|
81
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
82
|
+
}
|
|
83
|
+
return DoNotProcess;
|
|
84
|
+
}(Error));
|
|
85
|
+
exports.DoNotProcess = DoNotProcess;
|
|
86
|
+
var debug = (0, debug_1.default)('qdone:worker');
|
|
87
|
+
// Global flag for shutdown request
|
|
88
|
+
var shutdownRequested = false;
|
|
89
|
+
var shutdownCallbacks = [];
|
|
90
|
+
function requestShutdown() {
|
|
91
|
+
shutdownRequested = true;
|
|
92
|
+
for (var _i = 0, shutdownCallbacks_1 = shutdownCallbacks; _i < shutdownCallbacks_1.length; _i++) {
|
|
93
|
+
var callback = shutdownCallbacks_1[_i];
|
|
94
|
+
try {
|
|
95
|
+
callback();
|
|
96
|
+
}
|
|
97
|
+
catch (e) { }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.requestShutdown = requestShutdown;
|
|
101
|
+
function processMessage(message, callback, qname, qrl, opt) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
103
|
+
function extendTimeout() {
|
|
104
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
105
|
+
var maxJobRun, jobRunTime, result, err_2;
|
|
106
|
+
return __generator(this, function (_a) {
|
|
107
|
+
switch (_a.label) {
|
|
108
|
+
case 0:
|
|
109
|
+
debug('extendTimeout');
|
|
110
|
+
maxJobRun = 12 * 60 * 60;
|
|
111
|
+
jobRunTime = ((new Date()) - jobStart) / 1000;
|
|
112
|
+
// Double every time, up to max
|
|
113
|
+
visibilityTimeout = Math.min(visibilityTimeout * 2, maxJobRun - jobRunTime, opt.killAfter - jobRunTime);
|
|
114
|
+
if (opt.verbose) {
|
|
115
|
+
console.error(chalk_1.default.blue(' Ran for ') + jobRunTime +
|
|
116
|
+
chalk_1.default.blue(' seconds, requesting another ') + visibilityTimeout +
|
|
117
|
+
chalk_1.default.blue(' seconds'));
|
|
118
|
+
}
|
|
119
|
+
_a.label = 1;
|
|
120
|
+
case 1:
|
|
121
|
+
_a.trys.push([1, 3, , 4]);
|
|
122
|
+
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ChangeMessageVisibilityCommand({
|
|
123
|
+
QueueUrl: qrl,
|
|
124
|
+
ReceiptHandle: message.ReceiptHandle,
|
|
125
|
+
VisibilityTimeout: visibilityTimeout
|
|
126
|
+
}))];
|
|
127
|
+
case 2:
|
|
128
|
+
result = _a.sent();
|
|
129
|
+
debug('ChangeMessageVisibility returned', result);
|
|
130
|
+
if (jobRunTime + visibilityTimeout >= maxJobRun ||
|
|
131
|
+
jobRunTime + visibilityTimeout >= opt.killAfter) {
|
|
132
|
+
if (opt.verbose)
|
|
133
|
+
console.error(chalk_1.default.yellow(' warning: this is our last time extension'));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Extend when we get 50% of the way to timeout
|
|
137
|
+
timeoutExtender = setTimeout(extendTimeout, visibilityTimeout * 1000 * 0.5);
|
|
138
|
+
}
|
|
139
|
+
return [3 /*break*/, 4];
|
|
140
|
+
case 3:
|
|
141
|
+
err_2 = _a.sent();
|
|
142
|
+
debug('ChangeMessageVisibility threw', err_2);
|
|
143
|
+
// Rejection means we're ouuta time, whatever, let the job die
|
|
144
|
+
if (opt.verbose) {
|
|
145
|
+
console.error(chalk_1.default.red(' failed to extend job: ') + err_2);
|
|
146
|
+
}
|
|
147
|
+
else if (!opt.disableLog) {
|
|
148
|
+
// Production error logging
|
|
149
|
+
console.log(JSON.stringify({
|
|
150
|
+
event: 'MESSAGE_PROCESSING_FAILED',
|
|
151
|
+
reason: 'ran longer than --kill-after',
|
|
152
|
+
timestamp: new Date(),
|
|
153
|
+
messageId: message.MessageId,
|
|
154
|
+
payload: payload,
|
|
155
|
+
errorMessage: err_2.toString().split('\n').slice(1).join('\n').trim() || undefined,
|
|
156
|
+
err: err_2
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
return [3 /*break*/, 4];
|
|
160
|
+
case 4: return [2 /*return*/];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
var payload, jobStart, visibilityTimeout, timeoutExtender, queue, result, err_1, result;
|
|
166
|
+
return __generator(this, function (_a) {
|
|
167
|
+
switch (_a.label) {
|
|
168
|
+
case 0:
|
|
169
|
+
debug('processMessage', message, qname, qrl);
|
|
170
|
+
payload = JSON.parse(message.Body);
|
|
171
|
+
if (opt.verbose) {
|
|
172
|
+
console.error(chalk_1.default.blue(' Processing payload:'), payload);
|
|
173
|
+
}
|
|
174
|
+
else if (!opt.disableLog) {
|
|
175
|
+
console.log(JSON.stringify({
|
|
176
|
+
event: 'MESSAGE_PROCESSING_START',
|
|
177
|
+
timestamp: new Date(),
|
|
178
|
+
messageId: message.MessageId,
|
|
179
|
+
payload: payload
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
jobStart = new Date();
|
|
183
|
+
visibilityTimeout = 30 // this should be the queue timeout
|
|
184
|
+
;
|
|
185
|
+
// Extend when we get 50% of the way to timeout
|
|
186
|
+
timeoutExtender = setTimeout(extendTimeout, visibilityTimeout * 1000 * 0.5);
|
|
187
|
+
debug('timeout', visibilityTimeout * 1000 * 0.5);
|
|
188
|
+
_a.label = 1;
|
|
189
|
+
case 1:
|
|
190
|
+
_a.trys.push([1, 4, , 7]);
|
|
191
|
+
queue = qname.slice(opt.prefix.length);
|
|
192
|
+
return [4 /*yield*/, callback(queue, payload)];
|
|
193
|
+
case 2:
|
|
194
|
+
result = _a.sent();
|
|
195
|
+
debug('processMessage callback finished', { payload: payload, result: result });
|
|
196
|
+
clearTimeout(timeoutExtender);
|
|
197
|
+
if (opt.verbose) {
|
|
198
|
+
console.error(chalk_1.default.green(' SUCCESS'));
|
|
199
|
+
console.error(chalk_1.default.blue(' cleaning up (removing message) ...'));
|
|
200
|
+
}
|
|
201
|
+
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.DeleteMessageCommand({
|
|
202
|
+
QueueUrl: qrl,
|
|
203
|
+
ReceiptHandle: message.ReceiptHandle
|
|
204
|
+
}))];
|
|
205
|
+
case 3:
|
|
206
|
+
_a.sent();
|
|
207
|
+
if (opt.verbose) {
|
|
208
|
+
console.error(chalk_1.default.blue(' done'));
|
|
209
|
+
console.error();
|
|
210
|
+
}
|
|
211
|
+
else if (!opt.disableLog) {
|
|
212
|
+
console.log(JSON.stringify({
|
|
213
|
+
event: 'MESSAGE_PROCESSING_COMPLETE',
|
|
214
|
+
timestamp: new Date(),
|
|
215
|
+
messageId: message.MessageId,
|
|
216
|
+
payload: payload
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
return [2 /*return*/, { noJobs: 0, jobsSucceeded: 1, jobsFailed: 0 }];
|
|
220
|
+
case 4:
|
|
221
|
+
err_1 = _a.sent();
|
|
222
|
+
debug('exec.catch');
|
|
223
|
+
clearTimeout(timeoutExtender);
|
|
224
|
+
if (!(err_1 instanceof DoNotProcess)) return [3 /*break*/, 6];
|
|
225
|
+
if (opt.verbose) {
|
|
226
|
+
console.error(chalk_1.default.blue(' callback ') + chalk_1.default.yellow('REFUSED'));
|
|
227
|
+
console.error(chalk_1.default.blue(' cleaning up (removing message) ...'));
|
|
228
|
+
}
|
|
229
|
+
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ChangeMessageVisibilityCommand({
|
|
230
|
+
QueueUrl: qrl,
|
|
231
|
+
ReceiptHandle: message.ReceiptHandle,
|
|
232
|
+
VisibilityTimeout: 0
|
|
233
|
+
}))];
|
|
234
|
+
case 5:
|
|
235
|
+
result = _a.sent();
|
|
236
|
+
debug('ChangeMessageVisibility returned', result);
|
|
237
|
+
return [2 /*return*/, { noJobs: 1, jobsSucceeded: 0, jobsFailed: 0 }];
|
|
238
|
+
case 6:
|
|
239
|
+
// Fail path for job execution
|
|
240
|
+
if (opt.verbose) {
|
|
241
|
+
console.error(chalk_1.default.red(' FAILED'));
|
|
242
|
+
console.error(chalk_1.default.blue(' error : ') + err_1);
|
|
243
|
+
}
|
|
244
|
+
else if (!opt.disableLog) {
|
|
245
|
+
// Production error logging
|
|
246
|
+
console.log(JSON.stringify({
|
|
247
|
+
event: 'MESSAGE_PROCESSING_FAILED',
|
|
248
|
+
reason: 'exception thrown',
|
|
249
|
+
timestamp: new Date(),
|
|
250
|
+
messageId: message.MessageId,
|
|
251
|
+
payload: payload,
|
|
252
|
+
errorMessage: err_1.toString().split('\n').slice(1).join('\n').trim() || undefined,
|
|
253
|
+
err: err_1
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
return [2 /*return*/, { noJobs: 0, jobsSucceeded: 0, jobsFailed: 1 }];
|
|
257
|
+
case 7: return [2 /*return*/];
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
exports.processMessage = processMessage;
|
|
263
|
+
//
|
|
264
|
+
// Pull work off of a single queue
|
|
265
|
+
//
|
|
266
|
+
function pollSingleQueue(qname, qrl, callback, opt) {
|
|
267
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
268
|
+
var params, response, message;
|
|
269
|
+
return __generator(this, function (_a) {
|
|
270
|
+
switch (_a.label) {
|
|
271
|
+
case 0:
|
|
272
|
+
debug('pollSingleQueue', { qname: qname, qrl: qrl, callback: callback, opt: opt });
|
|
273
|
+
params = {
|
|
274
|
+
AttributeNames: ['All'],
|
|
275
|
+
MaxNumberOfMessages: 1,
|
|
276
|
+
MessageAttributeNames: ['All'],
|
|
277
|
+
QueueUrl: qrl,
|
|
278
|
+
VisibilityTimeout: 30,
|
|
279
|
+
WaitTimeSeconds: opt.waitTime
|
|
280
|
+
};
|
|
281
|
+
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ReceiveMessageCommand(params))];
|
|
282
|
+
case 1:
|
|
283
|
+
response = _a.sent();
|
|
284
|
+
debug('ReceiveMessage response', response);
|
|
285
|
+
if (shutdownRequested)
|
|
286
|
+
return [2 /*return*/, { noJobs: 0, jobsSucceeded: 0, jobsFailed: 0 }];
|
|
287
|
+
if (response.Messages) {
|
|
288
|
+
message = response.Messages[0];
|
|
289
|
+
if (opt.verbose)
|
|
290
|
+
console.error(chalk_1.default.blue(' Found message ' + message.MessageId));
|
|
291
|
+
return [2 /*return*/, processMessage(message, callback, qname, qrl, opt)];
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
return [2 /*return*/, { noJobs: 1, jobsSucceeded: 0, jobsFailed: 0 }];
|
|
295
|
+
}
|
|
296
|
+
return [2 /*return*/];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
exports.pollSingleQueue = pollSingleQueue;
|
|
302
|
+
//
|
|
303
|
+
// Resolve a set of queues
|
|
304
|
+
//
|
|
305
|
+
function resolveQueues(queues, opt) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
307
|
+
var qnames, pairs, activePairs, selectedPairs;
|
|
308
|
+
var _this = this;
|
|
309
|
+
return __generator(this, function (_a) {
|
|
310
|
+
switch (_a.label) {
|
|
311
|
+
case 0:
|
|
312
|
+
// Start processing
|
|
313
|
+
if (opt.verbose)
|
|
314
|
+
console.error(chalk_1.default.blue('Resolving queues: ') + queues.join(' '));
|
|
315
|
+
qnames = queues.map(function (queue) { return (0, qrlCache_js_1.normalizeQueueName)(queue, opt); });
|
|
316
|
+
return [4 /*yield*/, (0, qrlCache_js_1.getQnameUrlPairs)(qnames, opt)
|
|
317
|
+
// Figure out which pairs are active
|
|
318
|
+
];
|
|
319
|
+
case 1:
|
|
320
|
+
pairs = _a.sent();
|
|
321
|
+
activePairs = [];
|
|
322
|
+
if (!opt.activeOnly) return [3 /*break*/, 3];
|
|
323
|
+
debug({ pairsBeforeCheck: pairs });
|
|
324
|
+
return [4 /*yield*/, Promise.all(pairs.map(function (pair) { return __awaiter(_this, void 0, void 0, function () {
|
|
325
|
+
var idle;
|
|
326
|
+
return __generator(this, function (_a) {
|
|
327
|
+
switch (_a.label) {
|
|
328
|
+
case 0: return [4 /*yield*/, (0, idleQueues_js_1.cheapIdleCheck)(pair.qname, pair.qrl, opt)];
|
|
329
|
+
case 1:
|
|
330
|
+
idle = (_a.sent()).idle;
|
|
331
|
+
if (!idle)
|
|
332
|
+
activePairs.push(pair);
|
|
333
|
+
return [2 /*return*/];
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}); }))];
|
|
337
|
+
case 2:
|
|
338
|
+
_a.sent();
|
|
339
|
+
_a.label = 3;
|
|
340
|
+
case 3:
|
|
341
|
+
// Finished resolving
|
|
342
|
+
debug('getQnameUrlPairs.then');
|
|
343
|
+
if (opt.verbose) {
|
|
344
|
+
console.error(chalk_1.default.blue(' done'));
|
|
345
|
+
console.error();
|
|
346
|
+
}
|
|
347
|
+
selectedPairs = (opt.activeOnly ? activePairs : pairs)
|
|
348
|
+
.filter(function (_a) {
|
|
349
|
+
var qname = _a.qname;
|
|
350
|
+
var suf = opt.failSuffix + (opt.fifo ? '.fifo' : '');
|
|
351
|
+
var isFailQueue = qname.slice(-suf.length) === suf;
|
|
352
|
+
var shouldInclude = opt.includeFailed ? true : !isFailQueue;
|
|
353
|
+
return shouldInclude;
|
|
354
|
+
});
|
|
355
|
+
return [2 /*return*/, selectedPairs];
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
exports.resolveQueues = resolveQueues;
|
|
361
|
+
//
|
|
362
|
+
// Consumer
|
|
363
|
+
//
|
|
364
|
+
function processMessages(queues, callback, options) {
|
|
365
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
366
|
+
// Callback to help facilitate better UX at shutdown
|
|
367
|
+
function shutdownCallback() {
|
|
368
|
+
if (opt.verbose) {
|
|
369
|
+
debug({ activeLoops: activeLoops });
|
|
370
|
+
var activeQueues = Object.keys(activeLoops).filter(function (q) { return activeLoops[q]; }).map(function (q) { return q.slice(opt.prefix.length); });
|
|
371
|
+
if (activeQueues.length) {
|
|
372
|
+
console.error(chalk_1.default.blue('Waiting for work to finish on the following queues: ') + activeQueues.join(chalk_1.default.blue(', ')));
|
|
373
|
+
}
|
|
374
|
+
clearTimeout(delayTimeout);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Listen to a queue until it is out of messages
|
|
378
|
+
function listenLoop(qname, qrl) {
|
|
379
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
380
|
+
var _a, noJobs, jobsSucceeded, jobsFailed, err_3;
|
|
381
|
+
return __generator(this, function (_b) {
|
|
382
|
+
switch (_b.label) {
|
|
383
|
+
case 0:
|
|
384
|
+
_b.trys.push([0, 2, 3, 4]);
|
|
385
|
+
if (shutdownRequested)
|
|
386
|
+
return [2 /*return*/];
|
|
387
|
+
if (opt.verbose) {
|
|
388
|
+
console.error(chalk_1.default.blue('Looking for work on ') +
|
|
389
|
+
qname.slice(opt.prefix.length) +
|
|
390
|
+
chalk_1.default.blue(' (' + qrl + ')'));
|
|
391
|
+
}
|
|
392
|
+
return [4 /*yield*/, pollSingleQueue(qname, qrl, callback, opt)];
|
|
393
|
+
case 1:
|
|
394
|
+
_a = _b.sent(), noJobs = _a.noJobs, jobsSucceeded = _a.jobsSucceeded, jobsFailed = _a.jobsFailed;
|
|
395
|
+
stats.noJobs += noJobs;
|
|
396
|
+
stats.jobsFailed += jobsFailed;
|
|
397
|
+
stats.jobsSucceeded += jobsSucceeded;
|
|
398
|
+
// No work? return to outer loop
|
|
399
|
+
if (noJobs)
|
|
400
|
+
return [2 /*return*/];
|
|
401
|
+
// Otherwise keep going
|
|
402
|
+
return [2 /*return*/, listenLoop(qname, qrl)];
|
|
403
|
+
case 2:
|
|
404
|
+
err_3 = _b.sent();
|
|
405
|
+
// TODO: Sentry
|
|
406
|
+
console.error(chalk_1.default.red(' ERROR in listenLoop'));
|
|
407
|
+
console.error(chalk_1.default.blue(' error : ') + err_3);
|
|
408
|
+
return [3 /*break*/, 4];
|
|
409
|
+
case 3:
|
|
410
|
+
delete activeLoops[qname];
|
|
411
|
+
return [7 /*endfinally*/];
|
|
412
|
+
case 4: return [2 /*return*/];
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
var opt, stats, activeLoops, delayTimeout, delay, start, selectedPairs, _i, selectedPairs_1, _a, qname, qrl, msSoFar, msUntilNextResolve;
|
|
418
|
+
return __generator(this, function (_b) {
|
|
419
|
+
switch (_b.label) {
|
|
420
|
+
case 0:
|
|
421
|
+
opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
|
|
422
|
+
debug('processMessages', { queues: queues, callback: callback, options: options, opt: opt });
|
|
423
|
+
stats = { noJobs: 0, jobsSucceeded: 0, jobsFailed: 0 };
|
|
424
|
+
activeLoops = {};
|
|
425
|
+
delay = function (ms) { return new Promise(function (resolve) {
|
|
426
|
+
delayTimeout = setTimeout(resolve, ms);
|
|
427
|
+
}); };
|
|
428
|
+
shutdownCallbacks.push(shutdownCallback);
|
|
429
|
+
_b.label = 1;
|
|
430
|
+
case 1:
|
|
431
|
+
if (!!shutdownRequested) return [3 /*break*/, 5];
|
|
432
|
+
start = new Date();
|
|
433
|
+
return [4 /*yield*/, resolveQueues(queues, opt)];
|
|
434
|
+
case 2:
|
|
435
|
+
selectedPairs = _b.sent();
|
|
436
|
+
if (shutdownRequested)
|
|
437
|
+
return [3 /*break*/, 5];
|
|
438
|
+
// But only if we have queues to listen on
|
|
439
|
+
if (selectedPairs.length) {
|
|
440
|
+
// Randomize order
|
|
441
|
+
selectedPairs.sort(function () { return 0.5 - Math.random(); });
|
|
442
|
+
if (opt.verbose) {
|
|
443
|
+
console.error(chalk_1.default.blue('Listening to queues (in this order):'));
|
|
444
|
+
console.error(selectedPairs.map(function (_a) {
|
|
445
|
+
var qname = _a.qname, qrl = _a.qrl;
|
|
446
|
+
return ' ' + qname.slice(opt.prefix.length) + chalk_1.default.blue(' - ' + qrl);
|
|
447
|
+
}).join('\n'));
|
|
448
|
+
console.error();
|
|
449
|
+
}
|
|
450
|
+
// Launch listen loop for each queue
|
|
451
|
+
for (_i = 0, selectedPairs_1 = selectedPairs; _i < selectedPairs_1.length; _i++) {
|
|
452
|
+
_a = selectedPairs_1[_i], qname = _a.qname, qrl = _a.qrl;
|
|
453
|
+
if (!activeLoops[qname])
|
|
454
|
+
activeLoops[qname] = listenLoop(qname, qrl);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (!!shutdownRequested) return [3 /*break*/, 4];
|
|
458
|
+
msSoFar = Math.max(0, new Date() - start);
|
|
459
|
+
msUntilNextResolve = Math.max(0, opt.waitTime * 1000 - msSoFar);
|
|
460
|
+
debug({ msSoFar: msSoFar, msUntilNextResolve: msUntilNextResolve });
|
|
461
|
+
if (!msUntilNextResolve) return [3 /*break*/, 4];
|
|
462
|
+
if (opt.verbose)
|
|
463
|
+
console.error(chalk_1.default.blue('Will resolve queues again in ' + Math.round(msUntilNextResolve / 1000) + ' seconds'));
|
|
464
|
+
return [4 /*yield*/, delay(msUntilNextResolve)];
|
|
465
|
+
case 3:
|
|
466
|
+
_b.sent();
|
|
467
|
+
_b.label = 4;
|
|
468
|
+
case 4: return [3 /*break*/, 1];
|
|
469
|
+
case 5:
|
|
470
|
+
// Wait on all work to finish
|
|
471
|
+
// shutdownCallback()
|
|
472
|
+
return [4 /*yield*/, Promise.all(Object.values(activeLoops))];
|
|
473
|
+
case 6:
|
|
474
|
+
// Wait on all work to finish
|
|
475
|
+
// shutdownCallback()
|
|
476
|
+
_b.sent();
|
|
477
|
+
return [2 /*return*/];
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
exports.processMessages = processMessages;
|
|
483
|
+
debug('loaded');
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupVerbose = exports.setupAWS = exports.getOptionsWithDefaults = exports.defaults = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default options for qdone. Accepts a command line options object and
|
|
6
|
+
* returns nicely-named options.
|
|
7
|
+
*/
|
|
8
|
+
var uuid_1 = require("uuid");
|
|
9
|
+
exports.defaults = Object.freeze({
|
|
10
|
+
// Shared
|
|
11
|
+
prefix: 'qdone_',
|
|
12
|
+
failSuffix: '_failed',
|
|
13
|
+
region: 'us-east-1',
|
|
14
|
+
quiet: false,
|
|
15
|
+
verbose: false,
|
|
16
|
+
cache: false,
|
|
17
|
+
cacheUri: undefined,
|
|
18
|
+
cachePrefix: 'qdone:',
|
|
19
|
+
cacheTtlSeconds: 10,
|
|
20
|
+
fifo: false,
|
|
21
|
+
disableLog: false,
|
|
22
|
+
includeFailed: false,
|
|
23
|
+
// Enqueue
|
|
24
|
+
groupId: (0, uuid_1.v1)(),
|
|
25
|
+
groupIdPerMessage: false,
|
|
26
|
+
deduplicationId: (0, uuid_1.v1)(),
|
|
27
|
+
messageRetentionPeriod: 1209600,
|
|
28
|
+
delay: 0,
|
|
29
|
+
dlq: false,
|
|
30
|
+
dlqSuffix: '_dead',
|
|
31
|
+
dlqAfter: 3,
|
|
32
|
+
// Worker
|
|
33
|
+
waitTime: 20,
|
|
34
|
+
killAfter: 30,
|
|
35
|
+
archive: false,
|
|
36
|
+
activeOnly: false,
|
|
37
|
+
// Idle Queues
|
|
38
|
+
idleFor: 60,
|
|
39
|
+
delete: false,
|
|
40
|
+
unpair: false
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* This function should be called by each exposed API entry point on the
|
|
44
|
+
* options passed in from the caller. It supports options named in camelCase
|
|
45
|
+
* and also in command-line-style returned by our parsers.
|
|
46
|
+
*
|
|
47
|
+
* By convention, we name the variable "options" if it comes from the user
|
|
48
|
+
* and "opt" if it has already passed through this function.
|
|
49
|
+
*/
|
|
50
|
+
function getOptionsWithDefaults(options) {
|
|
51
|
+
// For API invocations don't force caller to supply default options
|
|
52
|
+
if (!options)
|
|
53
|
+
options = {};
|
|
54
|
+
// Activate DLQ if any option is set
|
|
55
|
+
var dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name']);
|
|
56
|
+
var opt = {
|
|
57
|
+
// Shared
|
|
58
|
+
prefix: options.prefix === '' ? options.prefix : (options.prefix || exports.defaults.prefix),
|
|
59
|
+
failSuffix: options.failSuffix || options['fail-suffix'] || exports.defaults.failSuffix,
|
|
60
|
+
region: options.region || process.env.AWS_REGION || exports.defaults.region,
|
|
61
|
+
quiet: options.quiet || exports.defaults.quiet,
|
|
62
|
+
verbose: options.verbose || exports.defaults.verbose,
|
|
63
|
+
fifo: options.fifo || exports.defaults.fifo,
|
|
64
|
+
sentryDsn: options.sentryDsn || options['sentry-dsn'],
|
|
65
|
+
disableLog: options.disableLog || options['disable-log'] || exports.defaults.disableLog,
|
|
66
|
+
// Cache
|
|
67
|
+
cacheUri: options.cacheUri || options['cache-uri'] || exports.defaults.cacheUri,
|
|
68
|
+
cachePrefix: options.cachePrefix || options['cache-prefix'] || exports.defaults.cachePrefix,
|
|
69
|
+
cacheTtlSeconds: options.cacheTtlSeconds || options['cache-ttl-seconds'] || exports.defaults.cacheTtlSeconds,
|
|
70
|
+
// Enqueue
|
|
71
|
+
groupId: options.groupId || options['group-id'] || exports.defaults.groupId,
|
|
72
|
+
groupIdPerMessage: false,
|
|
73
|
+
deduplicationId: options.deduplicationId || options['deduplication-id'] || exports.defaults.deduplicationId,
|
|
74
|
+
messageRetentionPeriod: options.messageRetentionPeriod || options['message-retention-period'] || exports.defaults.messageRetentionPeriod,
|
|
75
|
+
delay: options.delay || exports.defaults.delay,
|
|
76
|
+
dlq: dlq || exports.defaults.dlq,
|
|
77
|
+
dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || exports.defaults.dlqSuffix,
|
|
78
|
+
dlqAfter: options.dlqAfter || options['dlq-after'] || exports.defaults.dlqAfter,
|
|
79
|
+
tags: options.tags || undefined,
|
|
80
|
+
// Worker
|
|
81
|
+
waitTime: options.waitTime || options['wait-time'] || exports.defaults.waitTime,
|
|
82
|
+
killAfter: options.killAfter || options['kill-after'] || exports.defaults.killAfter,
|
|
83
|
+
archive: options.archive || exports.defaults.archive,
|
|
84
|
+
activeOnly: options.activeOnly || options['active-only'] || exports.defaults.activeOnly,
|
|
85
|
+
includeFailed: options.includeFailed || options['include-failed'] || exports.defaults.includeFailed,
|
|
86
|
+
// Idle Queues
|
|
87
|
+
idleFor: options.idleFor || options['idle-for'] || exports.defaults.idleFor,
|
|
88
|
+
delete: options.delete || exports.defaults.delete,
|
|
89
|
+
unpair: options.delete || exports.defaults.unpair
|
|
90
|
+
};
|
|
91
|
+
process.env.AWS_REGION = opt.region;
|
|
92
|
+
// TODO: validate options
|
|
93
|
+
return opt;
|
|
94
|
+
}
|
|
95
|
+
exports.getOptionsWithDefaults = getOptionsWithDefaults;
|
|
96
|
+
function setupAWS(options) {
|
|
97
|
+
var opt = getOptionsWithDefaults(options);
|
|
98
|
+
process.env.AWS_REGION = opt.region;
|
|
99
|
+
}
|
|
100
|
+
exports.setupAWS = setupAWS;
|
|
101
|
+
function setupVerbose(options) {
|
|
102
|
+
var verbose = options.verbose || (process.stderr.isTTY && !options.quiet);
|
|
103
|
+
var quiet = options.quiet || (!process.stderr.isTTY && !options.verbose);
|
|
104
|
+
options.verbose = verbose;
|
|
105
|
+
options.quiet = quiet;
|
|
106
|
+
}
|
|
107
|
+
exports.setupVerbose = setupVerbose;
|