qdone 2.0.29-alpha → 2.0.31-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/cache.js +22 -93
- package/commonjs/src/cloudWatch.js +65 -111
- package/commonjs/src/consumer.js +136 -237
- package/commonjs/src/defaults.js +11 -11
- package/commonjs/src/enqueue.js +311 -503
- package/commonjs/src/exponentialBackoff.js +39 -110
- package/commonjs/src/idleQueues.js +255 -396
- package/commonjs/src/monitor.js +42 -115
- package/commonjs/src/qrlCache.js +80 -162
- package/commonjs/src/scheduler/jobExecutor.js +324 -363
- package/commonjs/src/scheduler/queueManager.js +111 -198
- package/commonjs/src/scheduler/systemMonitor.js +24 -28
- package/commonjs/src/sqs.js +58 -141
- package/npm-shrinkwrap.json +15999 -0
- package/package.json +2 -2
- package/src/consumer.js +6 -7
- package/src/defaults.js +3 -3
- package/src/enqueue.js +1 -1
- package/src/scheduler/jobExecutor.js +97 -33
- package/src/scheduler/queueManager.js +1 -1
|
@@ -3,60 +3,27 @@
|
|
|
3
3
|
* Component to manage all the currently executing jobs, including extending
|
|
4
4
|
* their visibility timeouts and deleting them when they are successful.
|
|
5
5
|
*/
|
|
6
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
16
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
17
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
18
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
19
|
-
function step(op) {
|
|
20
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
21
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
22
|
-
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;
|
|
23
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
24
|
-
switch (op[0]) {
|
|
25
|
-
case 0: case 1: t = op; break;
|
|
26
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
27
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
28
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
29
|
-
default:
|
|
30
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
31
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
32
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
33
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
34
|
-
if (t[2]) _.ops.pop();
|
|
35
|
-
_.trys.pop(); continue;
|
|
36
|
-
}
|
|
37
|
-
op = body.call(thisArg, _);
|
|
38
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
39
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
8
|
};
|
|
45
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
10
|
exports.JobExecutor = void 0;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
11
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const debug_1 = __importDefault(require("debug"));
|
|
14
|
+
const sqs_js_1 = require("../sqs.js");
|
|
15
|
+
const debug = (0, debug_1.default)('qdone:jobExecutor');
|
|
16
|
+
const maxJobSeconds = 12 * 60 * 60;
|
|
17
|
+
class JobExecutor {
|
|
18
|
+
constructor(opt) {
|
|
55
19
|
this.opt = opt;
|
|
56
20
|
this.jobs = [];
|
|
57
21
|
this.jobsByMessageId = {};
|
|
22
|
+
this.jobsByQueue = new Map();
|
|
58
23
|
this.stats = {
|
|
59
24
|
activeJobs: 0,
|
|
25
|
+
waitingJobs: 0,
|
|
26
|
+
runningJobs: 0,
|
|
60
27
|
sqsCalls: 0,
|
|
61
28
|
timeoutsExtended: 0,
|
|
62
29
|
jobsSucceeded: 0,
|
|
@@ -66,330 +33,324 @@ var JobExecutor = /** @class */ (function () {
|
|
|
66
33
|
this.maintainPromise = this.maintainVisibility();
|
|
67
34
|
debug({ this: this });
|
|
68
35
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return [4 /*yield*/, this.maintainPromise];
|
|
81
|
-
case 1:
|
|
82
|
-
_a.sent();
|
|
83
|
-
return [4 /*yield*/, this.maintainVisibility()];
|
|
84
|
-
case 2:
|
|
85
|
-
_a.sent();
|
|
86
|
-
return [2 /*return*/];
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
JobExecutor.prototype.activeJobCount = function () {
|
|
36
|
+
async shutdown() {
|
|
37
|
+
this.shutdownRequested = true;
|
|
38
|
+
// Trigger a maintenance run right away in case it speeds us up
|
|
39
|
+
clearTimeout(this.maintainVisibilityTimeout);
|
|
40
|
+
if (this.opt.verbose) {
|
|
41
|
+
console.error(chalk_1.default.blue('Shutting down jobExecutor'));
|
|
42
|
+
}
|
|
43
|
+
await this.maintainPromise;
|
|
44
|
+
await this.maintainVisibility();
|
|
45
|
+
}
|
|
46
|
+
activeJobCount() {
|
|
92
47
|
return this.stats.activeJobs;
|
|
93
|
-
}
|
|
48
|
+
}
|
|
49
|
+
runningJobCount() {
|
|
50
|
+
return this.stats.runningJobs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the number of jobs running in a queue.
|
|
54
|
+
*/
|
|
55
|
+
runningJobCountForQueue(qname) {
|
|
56
|
+
const jobs = this.jobsByQueue.get(qname) || new Set();
|
|
57
|
+
let runningCount = 0;
|
|
58
|
+
for (const job of jobs.values())
|
|
59
|
+
runningCount += job.status === 'running';
|
|
60
|
+
return runningCount;
|
|
61
|
+
}
|
|
94
62
|
/**
|
|
95
63
|
* Changes message visibility on all running jobs using as few calls as possible.
|
|
96
64
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
jobsToExtendByQrl[job.qrl] = jobsToExtend;
|
|
148
|
-
doubled = job.visibilityTimeout * 2;
|
|
149
|
-
secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime);
|
|
150
|
-
// const secondsUntilKill = Math.max(1, this.opt.killAfter - jobRunTime)
|
|
151
|
-
job.visibilityTimeout = Math.min(doubled, secondsUntilMax); //, secondsUntilKill)
|
|
152
|
-
job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout / 2); // this is what we use next time
|
|
153
|
-
debug({ doubled: doubled, secondsUntilMax: secondsUntilMax, job: job });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
_a = jobsToExtendByQrl;
|
|
158
|
-
_b = [];
|
|
159
|
-
for (_c in _a)
|
|
160
|
-
_b.push(_c);
|
|
161
|
-
_i = 0;
|
|
162
|
-
_m.label = 1;
|
|
163
|
-
case 1:
|
|
164
|
-
if (!(_i < _b.length)) return [3 /*break*/, 5];
|
|
165
|
-
_c = _b[_i];
|
|
166
|
-
if (!(_c in _a)) return [3 /*break*/, 4];
|
|
167
|
-
qrl = _c;
|
|
168
|
-
jobsToExtend = jobsToExtendByQrl[qrl];
|
|
169
|
-
debug({ qrl: qrl, jobsToExtend: jobsToExtend });
|
|
170
|
-
_m.label = 2;
|
|
171
|
-
case 2:
|
|
172
|
-
if (!jobsToExtend.length) return [3 /*break*/, 4];
|
|
173
|
-
entries = [];
|
|
174
|
-
messageId = 0;
|
|
175
|
-
while (messageId++ < 10 && jobsToExtend.length) {
|
|
176
|
-
job = jobsToExtend.shift();
|
|
177
|
-
entry = {
|
|
178
|
-
Id: job.message.MessageId,
|
|
179
|
-
ReceiptHandle: job.message.ReceiptHandle,
|
|
180
|
-
VisibilityTimeout: job.visibilityTimeout
|
|
181
|
-
};
|
|
182
|
-
entries.push(entry);
|
|
183
|
-
}
|
|
184
|
-
debug({ entries: entries });
|
|
185
|
-
input = { QueueUrl: qrl, Entries: entries };
|
|
186
|
-
debug({ ChangeMessageVisibilityBatch: input });
|
|
187
|
-
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ChangeMessageVisibilityBatchCommand(input))];
|
|
188
|
-
case 3:
|
|
189
|
-
result = _m.sent();
|
|
190
|
-
debug('ChangeMessageVisibilityBatch returned', result);
|
|
191
|
-
this.stats.sqsCalls++;
|
|
192
|
-
if (result.Failed) {
|
|
193
|
-
console.error('FAILED_MESSAGES', result.Failed);
|
|
194
|
-
for (_d = 0, _e = result.Failed; _d < _e.length; _d++) {
|
|
195
|
-
failed = _e[_d];
|
|
196
|
-
console.error('FAILED_TO_EXTEND_JOB', this.jobsByMessageId[failed.Id]);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (result.Successful) {
|
|
200
|
-
count = result.Successful.length || 0;
|
|
201
|
-
this.stats.timeoutsExtended += count;
|
|
202
|
-
if (this.opt.verbose) {
|
|
203
|
-
console.error(chalk_1.default.blue('Extended'), count, chalk_1.default.blue('jobs'));
|
|
204
|
-
}
|
|
205
|
-
else if (!this.opt.disableLog) {
|
|
206
|
-
console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp: start, count: count, qrl: qrl }));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return [3 /*break*/, 2];
|
|
210
|
-
case 4:
|
|
211
|
-
_i++;
|
|
212
|
-
return [3 /*break*/, 1];
|
|
213
|
-
case 5:
|
|
214
|
-
_f = jobsToDeleteByQrl;
|
|
215
|
-
_g = [];
|
|
216
|
-
for (_h in _f)
|
|
217
|
-
_g.push(_h);
|
|
218
|
-
_j = 0;
|
|
219
|
-
_m.label = 6;
|
|
220
|
-
case 6:
|
|
221
|
-
if (!(_j < _g.length)) return [3 /*break*/, 10];
|
|
222
|
-
_h = _g[_j];
|
|
223
|
-
if (!(_h in _f)) return [3 /*break*/, 9];
|
|
224
|
-
qrl = _h;
|
|
225
|
-
jobsToDelete = jobsToDeleteByQrl[qrl];
|
|
226
|
-
_m.label = 7;
|
|
227
|
-
case 7:
|
|
228
|
-
if (!jobsToDelete.length) return [3 /*break*/, 9];
|
|
229
|
-
entries = [];
|
|
230
|
-
messageId = 0;
|
|
231
|
-
while (messageId++ < 10 && jobsToDelete.length) {
|
|
232
|
-
job = jobsToDelete.shift();
|
|
233
|
-
entry = {
|
|
234
|
-
Id: job.message.MessageId,
|
|
235
|
-
ReceiptHandle: job.message.ReceiptHandle,
|
|
236
|
-
VisibilityTimeout: job.visibilityTimeout
|
|
237
|
-
};
|
|
238
|
-
entries.push(entry);
|
|
239
|
-
}
|
|
240
|
-
debug({ entries: entries });
|
|
241
|
-
input = { QueueUrl: qrl, Entries: entries };
|
|
242
|
-
debug({ DeleteMessageBatch: input });
|
|
243
|
-
return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.DeleteMessageBatchCommand(input))];
|
|
244
|
-
case 8:
|
|
245
|
-
result = _m.sent();
|
|
246
|
-
this.stats.sqsCalls++;
|
|
247
|
-
if (result.Failed) {
|
|
248
|
-
console.error('FAILED_MESSAGES', result.Failed);
|
|
249
|
-
for (_k = 0, _l = result.Failed; _k < _l.length; _k++) {
|
|
250
|
-
failed = _l[_k];
|
|
251
|
-
console.error('FAILED_TO_DELETE_JOB', this.jobsByMessageId[failed.Id]);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (result.Successful) {
|
|
255
|
-
count = result.Successful.length || 0;
|
|
256
|
-
this.stats.jobsDeleted += count;
|
|
257
|
-
if (this.opt.verbose) {
|
|
258
|
-
console.error(chalk_1.default.blue('Deleted'), count, chalk_1.default.blue('jobs'));
|
|
259
|
-
}
|
|
260
|
-
else if (!this.opt.disableLog) {
|
|
261
|
-
console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp: start, count: count, qrl: qrl }));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
debug('DeleteMessageBatch returned', result);
|
|
265
|
-
return [3 /*break*/, 7];
|
|
266
|
-
case 9:
|
|
267
|
-
_j++;
|
|
268
|
-
return [3 /*break*/, 6];
|
|
269
|
-
case 10:
|
|
270
|
-
// Get rid of deleted and failed jobs
|
|
271
|
-
this.jobs = this.jobs.filter(function (job) {
|
|
272
|
-
if (job.status === 'deleting' || job.status === 'failed') {
|
|
273
|
-
debug('removed', job.message.MessageId);
|
|
274
|
-
delete _this.jobsByMessageId[job.message.MessageId];
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
return [2 /*return*/];
|
|
65
|
+
async maintainVisibility() {
|
|
66
|
+
// Bail if we are shutting down
|
|
67
|
+
if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) {
|
|
68
|
+
if (this.opt.verbose) {
|
|
69
|
+
console.error(chalk_1.default.blue('All workers done, finishing shutdown of jobExecutor'));
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Reset our timeout
|
|
74
|
+
clearTimeout(this.maintainVisibilityTimeout);
|
|
75
|
+
const nextCheckInMs = this.shutdownRequested ? 1000 : 10 * 1000;
|
|
76
|
+
this.maintainVisibilityTimeout = setTimeout(() => {
|
|
77
|
+
this.maintainPromise = this.maintainVisibility();
|
|
78
|
+
}, nextCheckInMs);
|
|
79
|
+
// debug('maintainVisibility', this.jobs)
|
|
80
|
+
const start = new Date();
|
|
81
|
+
const jobsToExtendByQrl = {};
|
|
82
|
+
const jobsToDeleteByQrl = {};
|
|
83
|
+
const jobsToCleanup = new Set();
|
|
84
|
+
// Build list of jobs we need to deal with
|
|
85
|
+
const jobStatuses = {};
|
|
86
|
+
for (let i = 0; i < this.jobs.length; i++) {
|
|
87
|
+
const job = this.jobs[i];
|
|
88
|
+
const jobRunTime = Math.round((start - job.start) / 1000);
|
|
89
|
+
jobStatuses[job.status] = (jobStatuses[job.status] || 0) + 1;
|
|
90
|
+
// debug('considering job', job)
|
|
91
|
+
if (job.status === 'complete') {
|
|
92
|
+
const jobsToDelete = jobsToDeleteByQrl[job.qrl] || [];
|
|
93
|
+
job.status = 'deleting';
|
|
94
|
+
jobsToDelete.push(job);
|
|
95
|
+
jobsToDeleteByQrl[job.qrl] = jobsToDelete;
|
|
96
|
+
}
|
|
97
|
+
else if (job.status === 'failed') {
|
|
98
|
+
jobsToCleanup.add(job);
|
|
99
|
+
}
|
|
100
|
+
else if (job.status !== 'deleting') {
|
|
101
|
+
// Any other job state gets visibility accounting
|
|
102
|
+
debug('processing', { job, jobRunTime });
|
|
103
|
+
if (jobRunTime >= job.extendAtSecond) {
|
|
104
|
+
// Add it to our organized list of jobs
|
|
105
|
+
const jobsToExtend = jobsToExtendByQrl[job.qrl] || [];
|
|
106
|
+
jobsToExtend.push(job);
|
|
107
|
+
jobsToExtendByQrl[job.qrl] = jobsToExtend;
|
|
108
|
+
// Update the visibility timeout, double every time, up to max
|
|
109
|
+
const doubled = job.visibilityTimeout * 2;
|
|
110
|
+
const secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime);
|
|
111
|
+
// const secondsUntilKill = Math.max(1, this.opt.killAfter - jobRunTime)
|
|
112
|
+
job.visibilityTimeout = Math.min(doubled, secondsUntilMax); //, secondsUntilKill)
|
|
113
|
+
job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout / 2); // this is what we use next time
|
|
114
|
+
debug({ doubled, secondsUntilMax, job });
|
|
282
115
|
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
timestamp: new Date(),
|
|
329
|
-
qrl: qrl,
|
|
330
|
-
messageId: message.MessageId,
|
|
331
|
-
payload: job.payload
|
|
332
|
-
}));
|
|
333
|
-
}
|
|
334
|
-
_a.label = 1;
|
|
335
|
-
case 1:
|
|
336
|
-
_a.trys.push([1, 3, , 4]);
|
|
337
|
-
queue = qname.slice(this.opt.prefix.length);
|
|
338
|
-
return [4 /*yield*/, callback(queue, payload)];
|
|
339
|
-
case 2:
|
|
340
|
-
result = _a.sent();
|
|
341
|
-
debug('executeJob callback finished', { payload: payload, result: result });
|
|
342
|
-
if (this.opt.verbose) {
|
|
343
|
-
console.error(chalk_1.default.green('SUCCESS'), message.Body);
|
|
344
|
-
}
|
|
345
|
-
job.status = 'complete';
|
|
346
|
-
if (this.opt.verbose) {
|
|
347
|
-
console.error(chalk_1.default.blue(' done'));
|
|
348
|
-
console.error();
|
|
349
|
-
}
|
|
350
|
-
else if (!this.opt.disableLog) {
|
|
351
|
-
console.log(JSON.stringify({
|
|
352
|
-
event: 'MESSAGE_PROCESSING_COMPLETE',
|
|
353
|
-
timestamp: new Date(),
|
|
354
|
-
messageId: message.MessageId,
|
|
355
|
-
payload: payload
|
|
356
|
-
}));
|
|
357
|
-
}
|
|
358
|
-
this.stats.jobsSucceeded++;
|
|
359
|
-
return [3 /*break*/, 4];
|
|
360
|
-
case 3:
|
|
361
|
-
err_1 = _a.sent();
|
|
362
|
-
// Notify caller that we failed
|
|
363
|
-
if (failedCallback)
|
|
364
|
-
failedCallback(message, qname, qrl);
|
|
365
|
-
// Fail path for job execution
|
|
366
|
-
if (this.opt.verbose) {
|
|
367
|
-
console.error(chalk_1.default.red('FAILED'), message.Body);
|
|
368
|
-
console.error(chalk_1.default.blue(' error : ') + err_1);
|
|
369
|
-
}
|
|
370
|
-
else if (!this.opt.disableLog) {
|
|
371
|
-
// Production error logging
|
|
372
|
-
console.log(JSON.stringify({
|
|
373
|
-
event: 'MESSAGE_PROCESSING_FAILED',
|
|
374
|
-
reason: 'exception thrown',
|
|
375
|
-
qrl: qrl,
|
|
376
|
-
timestamp: new Date(),
|
|
377
|
-
messageId: message.MessageId,
|
|
378
|
-
payload: payload,
|
|
379
|
-
errorMessage: err_1.toString().split('\n').slice(1).join('\n').trim() || undefined,
|
|
380
|
-
err: err_1
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
job.status = 'failed';
|
|
384
|
-
this.stats.jobsFailed++;
|
|
385
|
-
return [3 /*break*/, 4];
|
|
386
|
-
case 4:
|
|
387
|
-
this.stats.activeJobs--;
|
|
388
|
-
return [2 /*return*/];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (this.opt.verbose) {
|
|
119
|
+
console.error(chalk_1.default.blue('Stats: '), { stats: this.stats, jobStatuses });
|
|
120
|
+
console.error(chalk_1.default.blue('Running: '), this.jobs.filter(j => j.status === 'processing').map(({ qname, message }) => ({ qname, payload: message.Body })));
|
|
121
|
+
}
|
|
122
|
+
// Extend in batches for each queue
|
|
123
|
+
for (const qrl in jobsToExtendByQrl) {
|
|
124
|
+
const jobsToExtend = jobsToExtendByQrl[qrl];
|
|
125
|
+
debug({ qrl, jobsToExtend });
|
|
126
|
+
while (jobsToExtend.length) {
|
|
127
|
+
// Build list of messages to go in this batch
|
|
128
|
+
const entries = [];
|
|
129
|
+
let messageId = 0;
|
|
130
|
+
while (messageId++ < 10 && jobsToExtend.length) {
|
|
131
|
+
const job = jobsToExtend.shift();
|
|
132
|
+
const entry = {
|
|
133
|
+
Id: job.message.MessageId,
|
|
134
|
+
ReceiptHandle: job.message.ReceiptHandle,
|
|
135
|
+
VisibilityTimeout: job.visibilityTimeout
|
|
136
|
+
};
|
|
137
|
+
entries.push(entry);
|
|
138
|
+
}
|
|
139
|
+
debug({ entries });
|
|
140
|
+
// Change batch
|
|
141
|
+
const input = { QueueUrl: qrl, Entries: entries };
|
|
142
|
+
debug({ ChangeMessageVisibilityBatch: input });
|
|
143
|
+
const result = await (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ChangeMessageVisibilityBatchCommand(input));
|
|
144
|
+
debug('ChangeMessageVisibilityBatch returned', result);
|
|
145
|
+
this.stats.sqsCalls++;
|
|
146
|
+
if (result.Failed) {
|
|
147
|
+
console.error('FAILED_MESSAGES', result.Failed);
|
|
148
|
+
for (const failed of result.Failed) {
|
|
149
|
+
console.error('FAILED_TO_EXTEND_JOB', this.jobsByMessageId[failed.Id]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (result.Successful) {
|
|
153
|
+
const count = result.Successful.length || 0;
|
|
154
|
+
this.stats.timeoutsExtended += count;
|
|
155
|
+
if (this.opt.verbose) {
|
|
156
|
+
console.error(chalk_1.default.blue('Extended'), count, chalk_1.default.blue('jobs'));
|
|
157
|
+
}
|
|
158
|
+
else if (!this.opt.disableLog) {
|
|
159
|
+
console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp: start, count, qrl }));
|
|
160
|
+
}
|
|
389
161
|
}
|
|
390
|
-
|
|
162
|
+
// TODO Sentry
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Delete in batches for each queue
|
|
166
|
+
for (const qrl in jobsToDeleteByQrl) {
|
|
167
|
+
const jobsToDelete = jobsToDeleteByQrl[qrl];
|
|
168
|
+
while (jobsToDelete.length) {
|
|
169
|
+
// Build list of messages to go in this batch
|
|
170
|
+
const entries = [];
|
|
171
|
+
let messageId = 0;
|
|
172
|
+
while (messageId++ < 10 && jobsToDelete.length) {
|
|
173
|
+
const job = jobsToDelete.shift();
|
|
174
|
+
const entry = {
|
|
175
|
+
Id: job.message.MessageId,
|
|
176
|
+
ReceiptHandle: job.message.ReceiptHandle,
|
|
177
|
+
VisibilityTimeout: job.visibilityTimeout
|
|
178
|
+
};
|
|
179
|
+
entries.push(entry);
|
|
180
|
+
}
|
|
181
|
+
debug({ entries });
|
|
182
|
+
// Delete batch
|
|
183
|
+
const input = { QueueUrl: qrl, Entries: entries };
|
|
184
|
+
debug({ DeleteMessageBatch: input });
|
|
185
|
+
const result = await (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.DeleteMessageBatchCommand(input));
|
|
186
|
+
this.stats.sqsCalls++;
|
|
187
|
+
if (result.Failed) {
|
|
188
|
+
console.error('FAILED_MESSAGES', result.Failed);
|
|
189
|
+
for (const failed of result.Failed) {
|
|
190
|
+
console.error('FAILED_TO_DELETE_JOB', this.jobsByMessageId[failed.Id]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (result.Successful) {
|
|
194
|
+
const count = result.Successful.length || 0;
|
|
195
|
+
this.stats.jobsDeleted += count;
|
|
196
|
+
if (this.opt.verbose) {
|
|
197
|
+
console.error(chalk_1.default.blue('Deleted'), count, chalk_1.default.blue('jobs'));
|
|
198
|
+
}
|
|
199
|
+
else if (!this.opt.disableLog) {
|
|
200
|
+
console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp: start, count, qrl }));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
debug('DeleteMessageBatch returned', result);
|
|
204
|
+
// TODO Sentry
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Get rid of deleted and failed jobs
|
|
208
|
+
this.jobs = this.jobs.filter(job => {
|
|
209
|
+
if (job.status === 'deleting' || job.status === 'failed') {
|
|
210
|
+
debug('removed', job.message.MessageId);
|
|
211
|
+
// Accounting
|
|
212
|
+
delete this.jobsByMessageId[job.message.MessageId];
|
|
213
|
+
this.jobsByQueue.get(job.qname).delete(job);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
391
219
|
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
220
|
+
}
|
|
221
|
+
addJob(message, callback, qname, qrl) {
|
|
222
|
+
// Create job entry and track it
|
|
223
|
+
const defaultVisibilityTimeout = 60;
|
|
224
|
+
const job = {
|
|
225
|
+
status: 'waiting',
|
|
226
|
+
start: new Date(),
|
|
227
|
+
visibilityTimeout: defaultVisibilityTimeout,
|
|
228
|
+
extendAtSecond: defaultVisibilityTimeout / 2,
|
|
229
|
+
payload: this.opt.json ? JSON.parse(message.Body) : message.Body,
|
|
230
|
+
message,
|
|
231
|
+
callback,
|
|
232
|
+
qname,
|
|
233
|
+
prettyQname: qname.slice(this.opt.prefix.length),
|
|
234
|
+
qrl
|
|
235
|
+
};
|
|
236
|
+
// See if we are already executing this job
|
|
237
|
+
const oldJob = this.jobsByMessageId[job.message.MessageId];
|
|
238
|
+
if (oldJob) {
|
|
239
|
+
// If we actually see the same job again, we fucked up, probably due to
|
|
240
|
+
// the system being overloaded and us missing our extension call. So
|
|
241
|
+
// we'll celebrate this occasion by throwing a big fat error.
|
|
242
|
+
debug({ oldJob });
|
|
243
|
+
const e = new Error(`Saw job ${oldJob.message.MessageId} twice`);
|
|
244
|
+
e.job = oldJob;
|
|
245
|
+
// TODO: sentry breadcrumb
|
|
246
|
+
throw e;
|
|
247
|
+
}
|
|
248
|
+
// Accounting
|
|
249
|
+
this.jobs.push(job);
|
|
250
|
+
this.jobsByMessageId[job.message.MessageId] = job;
|
|
251
|
+
// Track all jobs for each queue
|
|
252
|
+
if (!this.jobsByQueue.has(job.qname))
|
|
253
|
+
this.jobsByQueue.set(job.qname, new Set());
|
|
254
|
+
this.jobsByQueue.get(job.qname).add(job);
|
|
255
|
+
this.stats.activeJobs++;
|
|
256
|
+
this.stats.waitingJobs++;
|
|
257
|
+
if (this.opt.verbose) {
|
|
258
|
+
console.error(chalk_1.default.blue('Got message:'), job.prettyQname, chalk_1.default.blue('-->'), job.payload, job.message.MessageId);
|
|
259
|
+
}
|
|
260
|
+
else if (!this.opt.disableLog) {
|
|
261
|
+
console.log(JSON.stringify({
|
|
262
|
+
event: 'MESSAGE_RECEIVED',
|
|
263
|
+
timestamp: new Date(),
|
|
264
|
+
queue: job.qname,
|
|
265
|
+
messageId: message.MessageId,
|
|
266
|
+
payload: job.payload
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
return job;
|
|
270
|
+
}
|
|
271
|
+
async runJob(job) {
|
|
272
|
+
try {
|
|
273
|
+
if (this.opt.verbose) {
|
|
274
|
+
console.error(chalk_1.default.blue('Running:'), job.prettyQname, chalk_1.default.blue('-->'), job.payload, job.message.MessageId);
|
|
275
|
+
}
|
|
276
|
+
else if (!this.opt.disableLog) {
|
|
277
|
+
console.log(JSON.stringify({
|
|
278
|
+
event: 'MESSAGE_PROCESSING_START',
|
|
279
|
+
timestamp: new Date(),
|
|
280
|
+
queue: job.qname,
|
|
281
|
+
messageId: job.message.MessageId,
|
|
282
|
+
payload: job.payload
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
job.status = 'running';
|
|
286
|
+
this.stats.runningJobs++;
|
|
287
|
+
this.stats.waitingJobs--;
|
|
288
|
+
const queue = job.qname.slice(this.opt.prefix.length);
|
|
289
|
+
const result = await job.callback(queue, job.payload);
|
|
290
|
+
debug('executeJob callback finished', { payload: job.payload, result });
|
|
291
|
+
if (this.opt.verbose) {
|
|
292
|
+
console.error(chalk_1.default.green('SUCCESS'), job.payload);
|
|
293
|
+
}
|
|
294
|
+
job.status = 'complete';
|
|
295
|
+
if (this.opt.verbose) {
|
|
296
|
+
console.error(chalk_1.default.blue(' done'));
|
|
297
|
+
console.error();
|
|
298
|
+
}
|
|
299
|
+
else if (!this.opt.disableLog) {
|
|
300
|
+
console.log(JSON.stringify({
|
|
301
|
+
event: 'MESSAGE_PROCESSING_COMPLETE',
|
|
302
|
+
queue: job.qname,
|
|
303
|
+
timestamp: new Date(),
|
|
304
|
+
messageId: job.message.MessageId,
|
|
305
|
+
payload: job.payload
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
this.stats.jobsSucceeded++;
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
job.status = 'failed';
|
|
312
|
+
this.stats.jobsFailed++;
|
|
313
|
+
// Fail path for job execution
|
|
314
|
+
if (this.opt.verbose) {
|
|
315
|
+
console.error(chalk_1.default.red('FAILED'), job.payload);
|
|
316
|
+
console.error(chalk_1.default.blue(' error : ') + err);
|
|
317
|
+
}
|
|
318
|
+
else if (!this.opt.disableLog) {
|
|
319
|
+
// Production error logging
|
|
320
|
+
console.log(JSON.stringify({
|
|
321
|
+
event: 'MESSAGE_PROCESSING_FAILED',
|
|
322
|
+
reason: 'exception thrown',
|
|
323
|
+
queue: job.qname,
|
|
324
|
+
timestamp: new Date(),
|
|
325
|
+
messageId: job.message.MessageId,
|
|
326
|
+
payload: job.payload,
|
|
327
|
+
errorMessage: err.toString().split('\n').slice(1).join('\n').trim() || undefined,
|
|
328
|
+
err
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
this.stats.activeJobs--;
|
|
333
|
+
this.stats.runningJobs--;
|
|
334
|
+
}
|
|
335
|
+
async executeJobs(messages, callback, qname, qrl) {
|
|
336
|
+
if (this.shutdownRequested)
|
|
337
|
+
throw new Error('jobExecutor is shutting down so cannot execute new jobs');
|
|
338
|
+
// Begin tracking jobs
|
|
339
|
+
const jobs = messages.map(message => this.addJob(message, callback, qname, qrl));
|
|
340
|
+
const isFifo = qrl.endsWith('.fifo');
|
|
341
|
+
// console.log(jobs)
|
|
342
|
+
// Begin executing
|
|
343
|
+
for (const [job, i] of jobs.map((job, i) => [job, i])) {
|
|
344
|
+
// Figure out if the next job needs to happen in serial, otherwise we can parallel execute
|
|
345
|
+
const nextJob = jobs[i + 1];
|
|
346
|
+
const nextJobIsSerial = isFifo && nextJob && job.message?.Attributes?.GroupId === nextJob.message?.Attributes?.GroupId;
|
|
347
|
+
// console.log({ i, nextJobAtt: nextJob?.message?.Attributes, nextJobIsSerial })
|
|
348
|
+
// Execute serial or parallel
|
|
349
|
+
if (nextJobIsSerial)
|
|
350
|
+
await this.runJob(job);
|
|
351
|
+
else
|
|
352
|
+
this.runJob(job);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
395
356
|
exports.JobExecutor = JobExecutor;
|