qdone 2.0.29-alpha → 2.0.30-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 +135 -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 +305 -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/package.json +2 -2
- package/src/consumer.js +3 -5
- package/src/defaults.js +3 -3
- package/src/enqueue.js +1 -1
- package/src/scheduler/jobExecutor.js +77 -33
- package/src/scheduler/queueManager.js +1 -1
package/commonjs/src/sqs.js
CHANGED
|
@@ -2,55 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Functions that deal with SQS
|
|
4
4
|
*/
|
|
5
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
-
});
|
|
13
|
-
};
|
|
14
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
15
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
16
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
17
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
18
|
-
function step(op) {
|
|
19
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
20
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
21
|
-
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;
|
|
22
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
23
|
-
switch (op[0]) {
|
|
24
|
-
case 0: case 1: t = op; break;
|
|
25
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
26
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
27
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
28
|
-
default:
|
|
29
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
30
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
31
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
32
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
33
|
-
if (t[2]) _.ops.pop();
|
|
34
|
-
_.trys.pop(); continue;
|
|
35
|
-
}
|
|
36
|
-
op = body.call(thisArg, _);
|
|
37
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
38
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
5
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
6
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
7
|
};
|
|
44
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
9
|
exports.getQueueAttributes = exports.getMatchingQueues = exports.setSQSClient = exports.getSQSClient = void 0;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
10
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const debug_1 = __importDefault(require("debug"));
|
|
13
|
+
const debug = (0, debug_1.default)('qdone:sqs');
|
|
50
14
|
/**
|
|
51
15
|
* Utility function to return an instantiated, shared SQSClient.
|
|
52
16
|
*/
|
|
53
|
-
|
|
17
|
+
let client;
|
|
54
18
|
function getSQSClient() {
|
|
55
19
|
if (client)
|
|
56
20
|
return client;
|
|
@@ -68,113 +32,66 @@ exports.setSQSClient = setSQSClient;
|
|
|
68
32
|
/**
|
|
69
33
|
* Returns qrls for queues matching the given prefix and regex.
|
|
70
34
|
*/
|
|
71
|
-
function getMatchingQueues(prefix, regex) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
result = _d.sent();
|
|
87
|
-
qrls = result.QueueUrls, nextToken2 = result.NextToken;
|
|
88
|
-
_b = (_a = (qrls || []).filter(function (q) { return regex.test(q); })).concat;
|
|
89
|
-
if (!nextToken2) return [3 /*break*/, 3];
|
|
90
|
-
return [4 /*yield*/, processQueues(nextToken2)];
|
|
91
|
-
case 2:
|
|
92
|
-
_c = _d.sent();
|
|
93
|
-
return [3 /*break*/, 4];
|
|
94
|
-
case 3:
|
|
95
|
-
_c = [];
|
|
96
|
-
_d.label = 4;
|
|
97
|
-
case 4:
|
|
98
|
-
// debug({ qrls, nextToken2 })
|
|
99
|
-
return [2 /*return*/, _b.apply(_a, [_c])];
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
var input, client;
|
|
105
|
-
return __generator(this, function (_a) {
|
|
106
|
-
input = { QueueNamePrefix: prefix, MaxResults: 1000 };
|
|
107
|
-
client = getSQSClient();
|
|
108
|
-
return [2 /*return*/, processQueues()];
|
|
109
|
-
});
|
|
110
|
-
});
|
|
35
|
+
async function getMatchingQueues(prefix, regex) {
|
|
36
|
+
const input = { QueueNamePrefix: prefix, MaxResults: 1000 };
|
|
37
|
+
const client = getSQSClient();
|
|
38
|
+
async function processQueues(nextToken) {
|
|
39
|
+
if (nextToken)
|
|
40
|
+
input.NextToken = nextToken;
|
|
41
|
+
const command = new client_sqs_1.ListQueuesCommand(input);
|
|
42
|
+
// debug({ nextToken, input, command })
|
|
43
|
+
const result = await client.send(command);
|
|
44
|
+
// debug({ result })
|
|
45
|
+
const { QueueUrls: qrls, NextToken: nextToken2 } = result;
|
|
46
|
+
// debug({ qrls, nextToken2 })
|
|
47
|
+
return (qrls || []).filter(q => regex.test(q)).concat(nextToken2 ? await processQueues(nextToken2) : []);
|
|
48
|
+
}
|
|
49
|
+
return processQueues();
|
|
111
50
|
}
|
|
112
51
|
exports.getMatchingQueues = getMatchingQueues;
|
|
113
52
|
/**
|
|
114
53
|
* Gets attributes on every queue in parallel.
|
|
115
54
|
*/
|
|
116
|
-
function getQueueAttributes(qrls) {
|
|
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
|
-
|
|
148
|
-
return [2 /*return*/, { queue: queue, result: result }];
|
|
149
|
-
case 3:
|
|
150
|
-
e_1 = _a.sent();
|
|
151
|
-
if (e_1 instanceof client_sqs_1.QueueDoesNotExist) {
|
|
152
|
-
// For queues that have been deleted in the meantime for whatever
|
|
153
|
-
// reason, just show as having no messages instead of failing the
|
|
154
|
-
// whole batch
|
|
155
|
-
return [2 /*return*/, {
|
|
156
|
-
queue: queue,
|
|
157
|
-
Attributes: {
|
|
158
|
-
ApproximateNumberOfMessages: '0',
|
|
159
|
-
ApproximateNumberOfMessagesNotVisible: '0',
|
|
160
|
-
ApproximateNumberOfMessagesDelayed: '0'
|
|
161
|
-
}
|
|
162
|
-
}];
|
|
163
|
-
}
|
|
164
|
-
throw e_1;
|
|
165
|
-
case 4: return [2 /*return*/];
|
|
55
|
+
async function getQueueAttributes(qrls) {
|
|
56
|
+
const promises = [];
|
|
57
|
+
// debug({ qrls })
|
|
58
|
+
for (const qrl of qrls) {
|
|
59
|
+
const input = {
|
|
60
|
+
QueueUrl: qrl,
|
|
61
|
+
AttributeNames: [
|
|
62
|
+
'ApproximateNumberOfMessages',
|
|
63
|
+
'ApproximateNumberOfMessagesNotVisible',
|
|
64
|
+
'ApproximateNumberOfMessagesDelayed'
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
const command = new client_sqs_1.GetQueueAttributesCommand(input);
|
|
68
|
+
// debug({ input, command })
|
|
69
|
+
promises.push((async () => {
|
|
70
|
+
const queue = (0, path_1.basename)(qrl);
|
|
71
|
+
try {
|
|
72
|
+
const result = await client.send(command);
|
|
73
|
+
// debug({ queue, result })
|
|
74
|
+
return { queue, result };
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e instanceof client_sqs_1.QueueDoesNotExist) {
|
|
78
|
+
// For queues that have been deleted in the meantime for whatever
|
|
79
|
+
// reason, just show as having no messages instead of failing the
|
|
80
|
+
// whole batch
|
|
81
|
+
return {
|
|
82
|
+
queue,
|
|
83
|
+
Attributes: {
|
|
84
|
+
ApproximateNumberOfMessages: '0',
|
|
85
|
+
ApproximateNumberOfMessagesNotVisible: '0',
|
|
86
|
+
ApproximateNumberOfMessagesDelayed: '0'
|
|
166
87
|
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// debug({ qrls })
|
|
171
|
-
for (_i = 0, qrls_1 = qrls; _i < qrls_1.length; _i++) {
|
|
172
|
-
qrl = qrls_1[_i];
|
|
173
|
-
_loop_1(qrl);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
throw e;
|
|
174
91
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
92
|
+
})());
|
|
93
|
+
}
|
|
94
|
+
return Promise.all(promises);
|
|
178
95
|
}
|
|
179
96
|
exports.getQueueAttributes = getQueueAttributes;
|
|
180
97
|
debug('loaded');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qdone",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.30-alpha",
|
|
4
4
|
"description": "Language agnostic job queue for SQS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"scripts": {
|
|
48
48
|
"start": "./src/bin.js",
|
|
49
49
|
"test": "NODE_OPTIONS='--experimental-json-modules --experimental-vm-modules --no-warnings' jest",
|
|
50
|
-
"build": "tsc --allowJs index.js --outdir commonjs --esModuleInterop --module commonjs",
|
|
50
|
+
"build": "tsc --allowJs index.js --outdir commonjs --esModuleInterop --module commonjs --target es2021 --strict --skipLibCheck --forceConsistentCasingInFileNames",
|
|
51
51
|
"clean": "rm -rf commonjs/src commonjs/*.js coverage",
|
|
52
52
|
"lint": "standard",
|
|
53
53
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
package/src/consumer.js
CHANGED
|
@@ -96,9 +96,7 @@ export async function processMessages (queues, callback, options) {
|
|
|
96
96
|
|
|
97
97
|
if (!shutdownRequested) {
|
|
98
98
|
if (messages.length) {
|
|
99
|
-
|
|
100
|
-
jobExecutor.executeJob(message, callback, qname, qrl)
|
|
101
|
-
}
|
|
99
|
+
jobExecutor.executeJobs(messages, callback, qname, qrl)
|
|
102
100
|
queueManager.updateIcehouse(qrl, false)
|
|
103
101
|
} else {
|
|
104
102
|
// If we didn't get any, update the icehouse so we can back off
|
|
@@ -107,7 +105,7 @@ export async function processMessages (queues, callback, options) {
|
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
// Max job accounting
|
|
110
|
-
maxReturnCount -=
|
|
108
|
+
if (messages.length) maxReturnCount -= messages.length
|
|
111
109
|
activeQrls.delete(qrl)
|
|
112
110
|
} catch (e) {
|
|
113
111
|
// If the queue has been cleaned up, we should back off anyway
|
|
@@ -146,7 +144,7 @@ export async function processMessages (queues, callback, options) {
|
|
|
146
144
|
let jobsLeft = targetJobs
|
|
147
145
|
|
|
148
146
|
if (opt.verbose) {
|
|
149
|
-
console.error({ maxConcurrentJobs: opt.maxConcurrentJobs, jobCount: jobExecutor.activeJobCount(), allowedJobs, maxLatency, latencyFactor, freememFactor, loadFactor, overallFactor, targetJobs, activeQrls })
|
|
147
|
+
// console.error({ maxConcurrentJobs: opt.maxConcurrentJobs, jobCount: jobExecutor.activeJobCount(), allowedJobs, maxLatency, latencyFactor, freememFactor, loadFactor, overallFactor, targetJobs, activeQrls })
|
|
150
148
|
}
|
|
151
149
|
for (const { qname, qrl } of queueManager.getPairs()) {
|
|
152
150
|
// debug({ evaluating: { qname, qrl, jobsLeft, activeQrlsHasQrl: activeQrls.has(qrl) } })
|
package/src/defaults.js
CHANGED
|
@@ -26,8 +26,8 @@ export const defaults = Object.freeze({
|
|
|
26
26
|
messageRetentionPeriod: 1209600,
|
|
27
27
|
delay: 0,
|
|
28
28
|
sendRetries: 6,
|
|
29
|
-
failDelay:
|
|
30
|
-
dlq:
|
|
29
|
+
failDelay: 120,
|
|
30
|
+
dlq: true,
|
|
31
31
|
dlqSuffix: '_dead',
|
|
32
32
|
dlqAfter: 3,
|
|
33
33
|
|
|
@@ -90,7 +90,7 @@ export function getOptionsWithDefaults (options) {
|
|
|
90
90
|
delay: options.delay || defaults.delay,
|
|
91
91
|
sendRetries: options['send-retries'] || defaults.sendRetries,
|
|
92
92
|
failDelay: options.failDelay || options['fail-delay'] || defaults.failDelay,
|
|
93
|
-
dlq: dlq || defaults.dlq,
|
|
93
|
+
dlq: dlq === false ? false : (dlq || defaults.dlq),
|
|
94
94
|
dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || defaults.dlqSuffix,
|
|
95
95
|
dlqAfter: options.dlqAfter || options['dlq-after'] || defaults.dlqAfter,
|
|
96
96
|
tags: options.tags || undefined,
|
package/src/enqueue.js
CHANGED
|
@@ -79,7 +79,7 @@ export async function getOrCreateFailQueue (queue, opt) {
|
|
|
79
79
|
maxReceiveCount: opt.dlqAfter + ''
|
|
80
80
|
})
|
|
81
81
|
}
|
|
82
|
-
if (opt.failDelay) params.Attributes.DelaySeconds = opt.failDelay
|
|
82
|
+
if (opt.failDelay) params.Attributes.DelaySeconds = opt.failDelay + ''
|
|
83
83
|
if (opt.tags) params.tags = opt.tags
|
|
84
84
|
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
85
85
|
const cmd = new CreateQueueCommand(params)
|
|
@@ -21,6 +21,8 @@ export class JobExecutor {
|
|
|
21
21
|
this.jobsByMessageId = {}
|
|
22
22
|
this.stats = {
|
|
23
23
|
activeJobs: 0,
|
|
24
|
+
waitingJobs: 0,
|
|
25
|
+
runningJobs: 0,
|
|
24
26
|
sqsCalls: 0,
|
|
25
27
|
timeoutsExtended: 0,
|
|
26
28
|
jobsSucceeded: 0,
|
|
@@ -71,15 +73,12 @@ export class JobExecutor {
|
|
|
71
73
|
const jobsToDeleteByQrl = {}
|
|
72
74
|
const jobsToCleanup = new Set()
|
|
73
75
|
|
|
74
|
-
if (this.opt.verbose) {
|
|
75
|
-
console.error(chalk.blue('Stats: '), this.stats)
|
|
76
|
-
console.error(chalk.blue('Running: '), this.jobs.filter(j => j.status === 'processing').map(({ qname, message }) => ({ qname, payload: message.Body })))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
76
|
// Build list of jobs we need to deal with
|
|
77
|
+
const jobStatuses = {}
|
|
80
78
|
for (let i = 0; i < this.jobs.length; i++) {
|
|
81
79
|
const job = this.jobs[i]
|
|
82
80
|
const jobRunTime = Math.round((start - job.start) / 1000)
|
|
81
|
+
jobStatuses[job.status] = (jobStatuses[job.status] || 0) + 1
|
|
83
82
|
// debug('considering job', job)
|
|
84
83
|
if (job.status === 'complete') {
|
|
85
84
|
const jobsToDelete = jobsToDeleteByQrl[job.qrl] || []
|
|
@@ -88,7 +87,8 @@ export class JobExecutor {
|
|
|
88
87
|
jobsToDeleteByQrl[job.qrl] = jobsToDelete
|
|
89
88
|
} else if (job.status === 'failed') {
|
|
90
89
|
jobsToCleanup.add(job)
|
|
91
|
-
} else if (job.status
|
|
90
|
+
} else if (job.status !== 'deleting') {
|
|
91
|
+
// Any other job state gets visibility accounting
|
|
92
92
|
debug('processing', { job, jobRunTime })
|
|
93
93
|
if (jobRunTime >= job.extendAtSecond) {
|
|
94
94
|
// Add it to our organized list of jobs
|
|
@@ -106,7 +106,11 @@ export class JobExecutor {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
|
|
110
|
+
if (this.opt.verbose) {
|
|
111
|
+
console.error(chalk.blue('Stats: '), { stats: this.stats, jobStatuses })
|
|
112
|
+
console.error(chalk.blue('Running: '), this.jobs.filter(j => j.status === 'processing').map(({ qname, message }) => ({ qname, payload: message.Body })))
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
// Extend in batches for each queue
|
|
112
116
|
for (const qrl in jobsToExtendByQrl) {
|
|
@@ -207,20 +211,19 @@ export class JobExecutor {
|
|
|
207
211
|
})
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
if (this.shutdownRequested) throw new Error('jobExecutor is shutting down so cannot execute new job')
|
|
214
|
+
addJob (message, callback, qname, qrl) {
|
|
212
215
|
// Create job entry and track it
|
|
213
|
-
const
|
|
214
|
-
const visibilityTimeout = 60
|
|
216
|
+
const defaultVisibilityTimeout = 60
|
|
215
217
|
const job = {
|
|
216
|
-
status: '
|
|
218
|
+
status: 'waiting',
|
|
217
219
|
start: new Date(),
|
|
218
|
-
visibilityTimeout,
|
|
219
|
-
extendAtSecond:
|
|
220
|
+
visibilityTimeout: defaultVisibilityTimeout,
|
|
221
|
+
extendAtSecond: defaultVisibilityTimeout / 2,
|
|
220
222
|
payload: this.opt.json ? JSON.parse(message.Body) : message.Body,
|
|
221
223
|
message,
|
|
222
224
|
callback,
|
|
223
225
|
qname,
|
|
226
|
+
prettyQname: qname.slice(this.opt.prefix.length),
|
|
224
227
|
qrl
|
|
225
228
|
}
|
|
226
229
|
|
|
@@ -237,29 +240,45 @@ export class JobExecutor {
|
|
|
237
240
|
throw e
|
|
238
241
|
}
|
|
239
242
|
|
|
240
|
-
// debug('executeJob', job)
|
|
241
243
|
this.jobs.push(job)
|
|
242
244
|
this.jobsByMessageId[job.message.MessageId] = job
|
|
243
245
|
this.stats.activeJobs++
|
|
246
|
+
this.stats.waitingJobs++
|
|
244
247
|
if (this.opt.verbose) {
|
|
245
|
-
console.error(chalk.blue('
|
|
248
|
+
console.error(chalk.blue('Got message:'), job.prettyQname, chalk.blue('-->'), job.payload, job.message.MessageId)
|
|
246
249
|
} else if (!this.opt.disableLog) {
|
|
247
250
|
console.log(JSON.stringify({
|
|
248
|
-
event: '
|
|
251
|
+
event: 'MESSAGE_RECEIVED',
|
|
249
252
|
timestamp: new Date(),
|
|
250
|
-
|
|
253
|
+
queue: job.qname,
|
|
251
254
|
messageId: message.MessageId,
|
|
252
255
|
payload: job.payload
|
|
253
256
|
}))
|
|
254
257
|
}
|
|
258
|
+
return job
|
|
259
|
+
}
|
|
255
260
|
|
|
256
|
-
|
|
261
|
+
async runJob (job) {
|
|
257
262
|
try {
|
|
258
|
-
const queue = qname.slice(this.opt.prefix.length)
|
|
259
|
-
const result = await callback(queue, payload)
|
|
260
|
-
debug('executeJob callback finished', { payload, result })
|
|
261
263
|
if (this.opt.verbose) {
|
|
262
|
-
console.error(chalk.
|
|
264
|
+
console.error(chalk.blue('Running:'), job.prettyQname, chalk.blue('-->'), job.payload, job.message.MessageId)
|
|
265
|
+
} else if (!this.opt.disableLog) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
event: 'MESSAGE_PROCESSING_START',
|
|
268
|
+
timestamp: new Date(),
|
|
269
|
+
queue: job.qname,
|
|
270
|
+
messageId: job.message.MessageId,
|
|
271
|
+
payload: job.payload
|
|
272
|
+
}))
|
|
273
|
+
}
|
|
274
|
+
job.status = 'running'
|
|
275
|
+
this.stats.runningJobs++
|
|
276
|
+
this.stats.waitingJobs--
|
|
277
|
+
const queue = job.qname.slice(this.opt.prefix.length)
|
|
278
|
+
const result = await job.callback(queue, job.payload)
|
|
279
|
+
debug('executeJob callback finished', { payload: job.payload, result })
|
|
280
|
+
if (this.opt.verbose) {
|
|
281
|
+
console.error(chalk.green('SUCCESS'), job.payload)
|
|
263
282
|
}
|
|
264
283
|
job.status = 'complete'
|
|
265
284
|
|
|
@@ -269,35 +288,60 @@ export class JobExecutor {
|
|
|
269
288
|
} else if (!this.opt.disableLog) {
|
|
270
289
|
console.log(JSON.stringify({
|
|
271
290
|
event: 'MESSAGE_PROCESSING_COMPLETE',
|
|
291
|
+
queue: job.qname,
|
|
272
292
|
timestamp: new Date(),
|
|
273
|
-
messageId: message.MessageId,
|
|
274
|
-
payload
|
|
293
|
+
messageId: job.message.MessageId,
|
|
294
|
+
payload: job.payload
|
|
275
295
|
}))
|
|
276
296
|
}
|
|
277
297
|
this.stats.jobsSucceeded++
|
|
278
298
|
} catch (err) {
|
|
279
|
-
|
|
280
|
-
|
|
299
|
+
job.status = 'failed'
|
|
300
|
+
this.stats.jobsFailed++
|
|
281
301
|
// Fail path for job execution
|
|
282
302
|
if (this.opt.verbose) {
|
|
283
|
-
console.error(chalk.red('FAILED'),
|
|
303
|
+
console.error(chalk.red('FAILED'), job.payload)
|
|
284
304
|
console.error(chalk.blue(' error : ') + err)
|
|
285
305
|
} else if (!this.opt.disableLog) {
|
|
286
306
|
// Production error logging
|
|
287
307
|
console.log(JSON.stringify({
|
|
288
308
|
event: 'MESSAGE_PROCESSING_FAILED',
|
|
289
309
|
reason: 'exception thrown',
|
|
290
|
-
|
|
310
|
+
queue: job.qname,
|
|
291
311
|
timestamp: new Date(),
|
|
292
|
-
messageId: message.MessageId,
|
|
293
|
-
payload,
|
|
312
|
+
messageId: job.message.MessageId,
|
|
313
|
+
payload: job.payload,
|
|
294
314
|
errorMessage: err.toString().split('\n').slice(1).join('\n').trim() || undefined,
|
|
295
315
|
err
|
|
296
316
|
}))
|
|
297
317
|
}
|
|
298
|
-
job.status = 'failed'
|
|
299
|
-
this.stats.jobsFailed++
|
|
300
318
|
}
|
|
301
319
|
this.stats.activeJobs--
|
|
320
|
+
this.stats.runningJobs--
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async executeJobs (messages, callback, qname, qrl) {
|
|
324
|
+
if (this.shutdownRequested) throw new Error('jobExecutor is shutting down so cannot execute new jobs')
|
|
325
|
+
|
|
326
|
+
// Begin tracking jobs
|
|
327
|
+
const jobs = messages.map(message => this.addJob(message, callback, qname, qrl))
|
|
328
|
+
const isFifo = qrl.endsWith('.fifo')
|
|
329
|
+
|
|
330
|
+
// console.log(jobs)
|
|
331
|
+
|
|
332
|
+
// Begin executing
|
|
333
|
+
for (const [job, i] of jobs.map((job, i) => [job, i])) {
|
|
334
|
+
// Figure out if the next job needs to happen in serial, otherwise we can parallel execute
|
|
335
|
+
// const job = jobs[i]
|
|
336
|
+
const nextJob = jobs[i + 1]
|
|
337
|
+
const nextJobIsSerial =
|
|
338
|
+
isFifo && nextJob &&
|
|
339
|
+
job.message?.Attributes?.GroupId === nextJob.message?.Attributes?.GroupId
|
|
340
|
+
|
|
341
|
+
console.log({ i, nextJobAtt: nextJob.message.Attributes, nextJobIsSerial })
|
|
342
|
+
// Execute serial or parallel
|
|
343
|
+
if (nextJobIsSerial) await this.runJob(job)
|
|
344
|
+
else this.runJob(job)
|
|
345
|
+
}
|
|
302
346
|
}
|
|
303
347
|
}
|
|
@@ -35,7 +35,7 @@ export class QueueManager {
|
|
|
35
35
|
const now = new Date()
|
|
36
36
|
const secondsElapsed = lastCheck - now
|
|
37
37
|
const minWait = 10
|
|
38
|
-
const maxWait =
|
|
38
|
+
const maxWait = 120
|
|
39
39
|
const baseSeconds = numEmptyReceives ** 2 * 20
|
|
40
40
|
const jitterSeconds = Math.round((Math.random() - 0.5) * baseSeconds)
|
|
41
41
|
const newSecondsToWait = Math.max(minWait, Math.min(maxWait, baseSeconds + jitterSeconds))
|