qdone 2.0.12-alpha → 2.0.14-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.
@@ -56,15 +56,34 @@ var debug = (0, debug_1.default)('qdone:consumer');
56
56
  var shutdownRequested = false;
57
57
  var shutdownCallbacks = [];
58
58
  function requestShutdown() {
59
- debug('requestShutdown');
60
- shutdownRequested = true;
61
- for (var _i = 0, shutdownCallbacks_1 = shutdownCallbacks; _i < shutdownCallbacks_1.length; _i++) {
62
- var callback = shutdownCallbacks_1[_i];
63
- debug('callback', callback);
64
- callback();
65
- // try { callback() } catch (e) { }
66
- }
67
- debug('requestShutdown done');
59
+ return __awaiter(this, void 0, void 0, function () {
60
+ var _i, shutdownCallbacks_1, callback;
61
+ return __generator(this, function (_a) {
62
+ switch (_a.label) {
63
+ case 0:
64
+ debug('requestShutdown');
65
+ shutdownRequested = true;
66
+ _i = 0, shutdownCallbacks_1 = shutdownCallbacks;
67
+ _a.label = 1;
68
+ case 1:
69
+ if (!(_i < shutdownCallbacks_1.length)) return [3 /*break*/, 4];
70
+ callback = shutdownCallbacks_1[_i];
71
+ debug('callback', callback);
72
+ return [4 /*yield*/, callback()
73
+ // try { callback() } catch (e) { }
74
+ ];
75
+ case 2:
76
+ _a.sent();
77
+ _a.label = 3;
78
+ case 3:
79
+ _i++;
80
+ return [3 /*break*/, 1];
81
+ case 4:
82
+ debug('requestShutdown done');
83
+ return [2 /*return*/];
84
+ }
85
+ });
86
+ });
68
87
  }
69
88
  exports.requestShutdown = requestShutdown;
70
89
  function getMessages(qrl, opt, maxMessages) {
@@ -78,7 +97,7 @@ function getMessages(qrl, opt, maxMessages) {
78
97
  MaxNumberOfMessages: maxMessages,
79
98
  MessageAttributeNames: ['All'],
80
99
  QueueUrl: qrl,
81
- VisibilityTimeout: 30,
100
+ VisibilityTimeout: 60,
82
101
  WaitTimeSeconds: opt.waitTime
83
102
  };
84
103
  return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ReceiveMessageCommand(params))
@@ -98,58 +117,101 @@ exports.getMessages = getMessages;
98
117
  //
99
118
  function processMessages(queues, callback, options) {
100
119
  return __awaiter(this, void 0, void 0, function () {
101
- var opt, systemMonitor, jobExecutor, queueManager, maxActiveJobs, delayTimeout, delay, activeQrls, maxReturnCount, listen, allowedJobs, maxLatency, latency, latencyFactor, targetJobs, jobsLeft, _i, _a, _b, qname, qrl, maxMessages;
120
+ var opt, lastLatency, systemMonitor, jobExecutor, queueManager, delayTimeout, delay, activeQrls, maxReturnCount, listen, allowedJobs, maxLatency, latency, latencyFactor, targetJobs, jobsLeft, _i, _a, _b, qname, qrl, maxMessages;
102
121
  var _this = this;
103
122
  return __generator(this, function (_c) {
104
123
  switch (_c.label) {
105
124
  case 0:
125
+ debug({ options: options });
106
126
  opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
107
- debug('processMessages', { queues: queues, callback: callback, options: options, opt: opt });
108
- systemMonitor = new systemMonitor_js_1.SystemMonitor(opt);
127
+ debug('processMessages', { queues: queues, callback: callback, options: options, opt: opt, argv: process.argv });
128
+ lastLatency = 10;
129
+ systemMonitor = new systemMonitor_js_1.SystemMonitor(function (latency) {
130
+ var percentDifference = 100 * Math.abs(lastLatency - latency) / lastLatency;
131
+ if (percentDifference > 10 && opt.verbose) {
132
+ console.error(chalk_1.default.blue('Latency:', Math.round(latency), 'ms'));
133
+ }
134
+ lastLatency = latency;
135
+ });
109
136
  jobExecutor = new jobExecutor_js_1.JobExecutor(opt);
110
137
  queueManager = new queueManager_js_1.QueueManager(opt, queues);
111
- maxActiveJobs = 100;
112
- debug({ systemMonitor: systemMonitor, jobExecutor: jobExecutor, queueManager: queueManager });
113
- shutdownCallbacks.push(function () {
114
- systemMonitor.shutdown();
115
- queueManager.shutdown();
116
- jobExecutor.shutdown();
117
- });
118
138
  delay = function (ms) { return new Promise(function (resolve) {
119
139
  delayTimeout = setTimeout(resolve, ms);
120
140
  }); };
141
+ shutdownCallbacks.push(function () { return __awaiter(_this, void 0, void 0, function () {
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0:
145
+ clearTimeout(delayTimeout);
146
+ return [4 /*yield*/, queueManager.shutdown()];
147
+ case 1:
148
+ _a.sent();
149
+ debug({ queueManager: 'done' });
150
+ return [4 /*yield*/, jobExecutor.shutdown()];
151
+ case 2:
152
+ _a.sent();
153
+ debug({ jobExecutor: 'done' });
154
+ return [4 /*yield*/, systemMonitor.shutdown()];
155
+ case 3:
156
+ _a.sent();
157
+ debug({ systemMonitor: 'done' });
158
+ return [2 /*return*/];
159
+ }
160
+ });
161
+ }); });
121
162
  activeQrls = new Set();
122
163
  maxReturnCount = 0;
123
164
  listen = function (qname, qrl, maxMessages) { return __awaiter(_this, void 0, void 0, function () {
124
- var messages, _i, messages_1, message;
165
+ var messages, _i, messages_1, message, e_1;
125
166
  return __generator(this, function (_a) {
126
167
  switch (_a.label) {
127
168
  case 0:
128
169
  activeQrls.add(qrl);
129
170
  maxReturnCount += maxMessages;
130
- return [4 /*yield*/, getMessages(qrl, opt, maxMessages)];
171
+ _a.label = 1;
131
172
  case 1:
173
+ _a.trys.push([1, 3, , 4]);
174
+ return [4 /*yield*/, getMessages(qrl, opt, maxMessages)];
175
+ case 2:
132
176
  messages = _a.sent();
133
- for (_i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
134
- message = messages_1[_i];
135
- jobExecutor.executeJob(message, callback, qname, qrl);
177
+ if (messages.length) {
178
+ for (_i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
179
+ message = messages_1[_i];
180
+ jobExecutor.executeJob(message, callback, qname, qrl);
181
+ }
182
+ queueManager.updateIcehouse(qrl, false);
183
+ }
184
+ else {
185
+ // If we didn't get any, update the icehouse so we can back off
186
+ queueManager.updateIcehouse(qrl, true);
136
187
  }
188
+ // Max job accounting
137
189
  maxReturnCount -= maxMessages;
138
190
  activeQrls.delete(qrl);
139
- return [2 /*return*/];
191
+ return [3 /*break*/, 4];
192
+ case 3:
193
+ e_1 = _a.sent();
194
+ // If the queue has been cleaned up, we should back off anyway
195
+ if (e_1 instanceof client_sqs_1.QueueDoesNotExist) {
196
+ queueManager.updateIcehouse(qrl, true);
197
+ }
198
+ else {
199
+ throw e_1;
200
+ }
201
+ return [3 /*break*/, 4];
202
+ case 4: return [2 /*return*/];
140
203
  }
141
204
  });
142
205
  }); };
143
206
  _c.label = 1;
144
207
  case 1:
145
208
  if (!!shutdownRequested) return [3 /*break*/, 3];
146
- allowedJobs = maxActiveJobs - jobExecutor.activeJobCount() - maxReturnCount;
209
+ allowedJobs = opt.maxConcurrentJobs - jobExecutor.activeJobCount() - maxReturnCount;
147
210
  maxLatency = 100;
148
211
  latency = systemMonitor.getLatency().setTimeout;
149
212
  latencyFactor = 1 - Math.abs(Math.min(latency / maxLatency, 1)) // 0 if latency is at max, 1 if latency 0
150
213
  ;
151
214
  targetJobs = Math.round(allowedJobs * latencyFactor);
152
- debug({ allowedJobs: allowedJobs, maxLatency: maxLatency, latency: latency, latencyFactor: latencyFactor, targetJobs: targetJobs, activeQrls: activeQrls });
153
215
  jobsLeft = targetJobs;
154
216
  for (_i = 0, _a = queueManager.getPairs(); _i < _a.length; _i++) {
155
217
  _b = _a[_i], qname = _b.qname, qrl = _b.qrl;
@@ -161,7 +223,7 @@ function processMessages(queues, callback, options) {
161
223
  if (opt.verbose) {
162
224
  console.error(chalk_1.default.blue('Listening on: '), qname);
163
225
  }
164
- debug({ listenedTo: { qname: qname, maxMessages: maxMessages, jobsLeft: jobsLeft } });
226
+ // debug({ listenedTo: { qname, maxMessages, jobsLeft } })
165
227
  }
166
228
  return [4 /*yield*/, delay(1000)];
167
229
  case 2:
@@ -36,11 +36,18 @@ exports.defaults = Object.freeze({
36
36
  killAfter: 30,
37
37
  archive: false,
38
38
  activeOnly: false,
39
+ maxConcurrentJobs: 100,
39
40
  // Idle Queues
40
41
  idleFor: 60,
41
42
  delete: false,
42
43
  unpair: false
43
44
  });
45
+ function validateInteger(opt, name) {
46
+ var parsed = parseInt(opt[name], 10);
47
+ if (isNaN(parsed))
48
+ throw new Error("".concat(name, " needs to be an integer."));
49
+ return parsed;
50
+ }
44
51
  /**
45
52
  * This function should be called by each exposed API entry point on the
46
53
  * options passed in from the caller. It supports options named in camelCase
@@ -87,13 +94,25 @@ function getOptionsWithDefaults(options) {
87
94
  archive: options.archive || exports.defaults.archive,
88
95
  activeOnly: options.activeOnly || options['active-only'] || exports.defaults.activeOnly,
89
96
  includeFailed: options.includeFailed || options['include-failed'] || exports.defaults.includeFailed,
97
+ maxConcurrentJobs: options.maxConcurrentJobs || exports.defaults.maxConcurrentJobs,
90
98
  // Idle Queues
91
99
  idleFor: options.idleFor || options['idle-for'] || exports.defaults.idleFor,
92
100
  delete: options.delete || exports.defaults.delete,
93
101
  unpair: options.delete || exports.defaults.unpair
94
102
  };
103
+ // Setting this env here means we don't have to in AWS SDK constructors
95
104
  process.env.AWS_REGION = opt.region;
96
- // TODO: validate options
105
+ // Validation
106
+ opt.cacheTtlSeconds = validateInteger(opt, 'cacheTtlSeconds');
107
+ opt.messageRetentionPeriod = validateInteger(opt, 'messageRetentionPeriod');
108
+ opt.delay = validateInteger(opt, 'delay');
109
+ opt.sendRetries = validateInteger(opt, 'sendRetries');
110
+ opt.failDelay = validateInteger(opt, 'failDelay');
111
+ opt.dlqAfter = validateInteger(opt, 'dlqAfter');
112
+ opt.waitTime = validateInteger(opt, 'waitTime');
113
+ opt.killAfter = validateInteger(opt, 'killAfter');
114
+ opt.maxConcurrentJobs = validateInteger(opt, 'maxConcurrentJobs');
115
+ opt.idleFor = validateInteger(opt, 'idleFor');
97
116
  return opt;
98
117
  }
99
118
  exports.getOptionsWithDefaults = getOptionsWithDefaults;
@@ -91,11 +91,10 @@ function _cheapIdleCheck(qname, qrl, opt) {
91
91
  case 0:
92
92
  client = (0, sqs_js_1.getSQSClient)();
93
93
  cmd = new client_sqs_1.GetQueueAttributesCommand({ AttributeNames: exports.attributeNames, QueueUrl: qrl });
94
- return [4 /*yield*/, client.send(cmd)
95
- // debug('data', data)
96
- ];
94
+ return [4 /*yield*/, client.send(cmd)];
97
95
  case 1:
98
96
  data = _a.sent();
97
+ debug('data', data);
99
98
  result = data.Attributes;
100
99
  result.queue = qname.slice(opt.prefix.length);
101
100
  // We are idle if all the messages attributes are zero
@@ -54,6 +54,7 @@ var JobExecutor = /** @class */ (function () {
54
54
  function JobExecutor(opt) {
55
55
  this.opt = opt;
56
56
  this.jobs = [];
57
+ this.jobsByMessageId = {};
57
58
  this.stats = {
58
59
  activeJobs: 0,
59
60
  sqsCalls: 0,
@@ -62,14 +63,27 @@ var JobExecutor = /** @class */ (function () {
62
63
  jobsFailed: 0,
63
64
  jobsDeleted: 0
64
65
  };
65
- this.maintainVisibility();
66
+ this.maintainPromise = this.maintainVisibility();
66
67
  debug({ this: this });
67
68
  }
68
69
  JobExecutor.prototype.shutdown = function () {
69
- this.shutdownRequested = true;
70
- if (this.stats.activeJobs === 0 && this.jobs.length === 0) {
71
- clearTimeout(this.maintainVisibilityTimeout);
72
- }
70
+ return __awaiter(this, void 0, void 0, function () {
71
+ return __generator(this, function (_a) {
72
+ switch (_a.label) {
73
+ case 0:
74
+ this.shutdownRequested = true;
75
+ // Trigger a maintenance run right away in case it speeds us up
76
+ clearTimeout(this.maintainVisibilityTimeout);
77
+ return [4 /*yield*/, this.maintainPromise];
78
+ case 1:
79
+ _a.sent();
80
+ return [4 /*yield*/, this.maintainVisibility()];
81
+ case 2:
82
+ _a.sent();
83
+ return [2 /*return*/];
84
+ }
85
+ });
86
+ });
73
87
  };
74
88
  JobExecutor.prototype.activeJobCount = function () {
75
89
  return this.stats.activeJobs;
@@ -79,13 +93,13 @@ var JobExecutor = /** @class */ (function () {
79
93
  */
80
94
  JobExecutor.prototype.maintainVisibility = function () {
81
95
  return __awaiter(this, void 0, void 0, function () {
82
- var now, jobsToExtendByQrl, jobsToDeleteByQrl, jobsToCleanup, _i, _a, job, jobRunTime, jobsToDelete, jobsToExtend, doubled, secondsUntilMax, secondsUntilKill, _b, _c, _d, _e, qrl, jobsToExtend, entries, messageId, job, entry, input, result, _f, _g, _h, _j, qrl, jobsToDelete, entries, messageId, job, entry, input, result;
96
+ var start, jobsToExtendByQrl, jobsToDeleteByQrl, jobsToCleanup, i, job, jobRunTime, jobsToDelete, jobsToExtend, doubled, secondsUntilMax, _a, _b, _c, _i, qrl, jobsToExtend, entries, messageId, job, entry, input, result, count, _d, _e, _f, _g, qrl, jobsToDelete, entries, messageId, job, entry, input, result, count, msElapsed, msPeriod, msLeft, msMin, nextCheckInMs;
83
97
  var _this = this;
84
- return __generator(this, function (_k) {
85
- switch (_k.label) {
98
+ return __generator(this, function (_h) {
99
+ switch (_h.label) {
86
100
  case 0:
87
- debug('maintainVisibility', this.jobs);
88
- now = new Date();
101
+ clearTimeout(this.maintainVisibilityTimeout);
102
+ start = new Date();
89
103
  jobsToExtendByQrl = {};
90
104
  jobsToDeleteByQrl = {};
91
105
  jobsToCleanup = new Set();
@@ -97,43 +111,48 @@ var JobExecutor = /** @class */ (function () {
97
111
  }));
98
112
  }
99
113
  // Build list of jobs we need to deal with
100
- for (_i = 0, _a = this.jobs; _i < _a.length; _i++) {
101
- job = _a[_i];
102
- jobRunTime = (now - job.start) / 1000;
114
+ for (i = 0; i < this.jobs.length; i++) {
115
+ job = this.jobs[i];
116
+ jobRunTime = Math.round((start - job.start) / 1000);
117
+ // debug('considering job', job)
103
118
  if (job.status === 'complete') {
104
119
  jobsToDelete = jobsToDeleteByQrl[job.qrl] || [];
120
+ job.status = 'deleting';
105
121
  jobsToDelete.push(job);
106
122
  jobsToDeleteByQrl[job.qrl] = jobsToDelete;
107
123
  }
108
124
  else if (job.status === 'failed') {
109
125
  jobsToCleanup.add(job);
110
126
  }
111
- else if (jobRunTime >= job.exendAtSecond) {
112
- jobsToExtend = jobsToExtendByQrl[job.qrl] || [];
113
- jobsToExtend.push(job);
114
- jobsToExtendByQrl[job.qrl] = jobsToExtend;
115
- doubled = job.visibilityTimeout * 2;
116
- secondsUntilMax = maxJobSeconds - jobRunTime;
117
- secondsUntilKill = this.opt.killAfter - jobRunTime;
118
- job.visibilityTimeout = Math.min(double, secondsUntilMax, secondsUntilKill);
119
- job.extendAtSecond = jobRunTime + job.visibilityTimeout; // this is what we use next time
127
+ else if (job.status === 'processing') {
128
+ debug('processing', { job: job, jobRunTime: jobRunTime });
129
+ if (jobRunTime >= job.extendAtSecond) {
130
+ jobsToExtend = jobsToExtendByQrl[job.qrl] || [];
131
+ jobsToExtend.push(job);
132
+ jobsToExtendByQrl[job.qrl] = jobsToExtend;
133
+ doubled = job.visibilityTimeout * 2;
134
+ secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime);
135
+ // const secondsUntilKill = Math.max(1, this.opt.killAfter - jobRunTime)
136
+ job.visibilityTimeout = Math.min(doubled, secondsUntilMax); //, secondsUntilKill)
137
+ job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout); // this is what we use next time
138
+ debug({ doubled: doubled, secondsUntilMax: secondsUntilMax, job: job });
139
+ }
120
140
  }
121
141
  }
122
- debug('maintainVisibility', { jobsToDeleteByQrl: jobsToDeleteByQrl, jobsToExtendByQrl: jobsToExtendByQrl });
123
- _b = jobsToExtendByQrl;
124
- _c = [];
125
- for (_d in _b)
126
- _c.push(_d);
127
- _e = 0;
128
- _k.label = 1;
142
+ _a = jobsToExtendByQrl;
143
+ _b = [];
144
+ for (_c in _a)
145
+ _b.push(_c);
146
+ _i = 0;
147
+ _h.label = 1;
129
148
  case 1:
130
- if (!(_e < _c.length)) return [3 /*break*/, 6];
131
- _d = _c[_e];
132
- if (!(_d in _b)) return [3 /*break*/, 5];
133
- qrl = _d;
149
+ if (!(_i < _b.length)) return [3 /*break*/, 5];
150
+ _c = _b[_i];
151
+ if (!(_c in _a)) return [3 /*break*/, 4];
152
+ qrl = _c;
134
153
  jobsToExtend = jobsToExtendByQrl[qrl];
135
154
  debug({ qrl: qrl, jobsToExtend: jobsToExtend });
136
- _k.label = 2;
155
+ _h.label = 2;
137
156
  case 2:
138
157
  if (!jobsToExtend.length) return [3 /*break*/, 4];
139
158
  entries = [];
@@ -147,50 +166,44 @@ var JobExecutor = /** @class */ (function () {
147
166
  };
148
167
  entries.push(entry);
149
168
  }
169
+ debug({ entries: entries });
150
170
  input = { QueueUrl: qrl, Entries: entries };
151
171
  debug({ ChangeMessageVisibilityBatch: input });
152
172
  return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.ChangeMessageVisibilityBatchCommand(input))];
153
173
  case 3:
154
- result = _k.sent();
174
+ result = _h.sent();
155
175
  debug('ChangeMessageVisibilityBatch returned', result);
156
176
  this.stats.sqsCalls++;
157
- if (result.Successful)
158
- this.stats.timeoutsExtended += result.Successful.length || 0;
177
+ if (result.Successful) {
178
+ count = result.Successful.length || 0;
179
+ this.stats.timeoutsExtended += count;
180
+ if (this.opt.verbose) {
181
+ console.error(chalk_1.default.blue('Extended'), count, chalk_1.default.blue('jobs'));
182
+ }
183
+ else if (!this.opt.disableLog) {
184
+ console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp: start, count: count, qrl: qrl }));
185
+ }
186
+ }
159
187
  return [3 /*break*/, 2];
160
188
  case 4:
161
- if (this.opt.verbose) {
162
- console.error(chalk_1.default.blue('Extended these jobs: '), jobsToExtend);
163
- }
164
- else if (!this.opt.disableLog) {
165
- console.log(JSON.stringify({
166
- event: 'EXTEND_VISIBILITY_TIMEOUTS',
167
- timestamp: now,
168
- messageIds: jobsToExtend.map(function (_a) {
169
- var message = _a.message;
170
- return message.MessageId;
171
- })
172
- }));
173
- }
174
- _k.label = 5;
175
- case 5:
176
- _e++;
189
+ _i++;
177
190
  return [3 /*break*/, 1];
191
+ case 5:
192
+ _d = jobsToDeleteByQrl;
193
+ _e = [];
194
+ for (_f in _d)
195
+ _e.push(_f);
196
+ _g = 0;
197
+ _h.label = 6;
178
198
  case 6:
179
- _f = jobsToDeleteByQrl;
180
- _g = [];
181
- for (_h in _f)
182
- _g.push(_h);
183
- _j = 0;
184
- _k.label = 7;
185
- case 7:
186
- if (!(_j < _g.length)) return [3 /*break*/, 12];
187
- _h = _g[_j];
188
- if (!(_h in _f)) return [3 /*break*/, 11];
189
- qrl = _h;
199
+ if (!(_g < _e.length)) return [3 /*break*/, 10];
200
+ _f = _e[_g];
201
+ if (!(_f in _d)) return [3 /*break*/, 9];
202
+ qrl = _f;
190
203
  jobsToDelete = jobsToDeleteByQrl[qrl];
191
- _k.label = 8;
192
- case 8:
193
- if (!jobsToDelete.length) return [3 /*break*/, 10];
204
+ _h.label = 7;
205
+ case 7:
206
+ if (!jobsToDelete.length) return [3 /*break*/, 9];
194
207
  entries = [];
195
208
  messageId = 0;
196
209
  while (messageId++ < 10 && jobsToDelete.length) {
@@ -202,41 +215,52 @@ var JobExecutor = /** @class */ (function () {
202
215
  };
203
216
  entries.push(entry);
204
217
  }
218
+ debug({ entries: entries });
205
219
  input = { QueueUrl: qrl, Entries: entries };
206
220
  debug({ DeleteMessageBatch: input });
207
221
  return [4 /*yield*/, (0, sqs_js_1.getSQSClient)().send(new client_sqs_1.DeleteMessageBatchCommand(input))];
208
- case 9:
209
- result = _k.sent();
222
+ case 8:
223
+ result = _h.sent();
210
224
  this.stats.sqsCalls++;
211
- if (result.Successful)
212
- this.stats.jobsDeleted += result.Successful.length || 0;
213
- debug('DeleteMessageBatch returned', result);
214
- return [3 /*break*/, 8];
215
- case 10:
216
- if (this.opt.verbose) {
217
- console.error(chalk_1.default.blue('Deleted these finished jobs: '), jobsToDelete);
218
- }
219
- else if (!this.opt.disableLog) {
220
- console.log(JSON.stringify({
221
- event: 'DELETE_MESSAGES',
222
- timestamp: now,
223
- messageIds: jobsToDelete.map(function (_a) {
224
- var message = _a.message;
225
- return message.MessageId;
226
- })
227
- }));
225
+ if (result.Successful) {
226
+ count = result.Successful.length || 0;
227
+ this.stats.jobsDeleted += count;
228
+ if (this.opt.verbose) {
229
+ console.error(chalk_1.default.blue('Deleted'), count, chalk_1.default.blue('jobs'));
230
+ }
231
+ else if (!this.opt.disableLog) {
232
+ console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp: start, count: count, qrl: qrl }));
233
+ }
228
234
  }
229
- _k.label = 11;
230
- case 11:
231
- _j++;
235
+ debug('DeleteMessageBatch returned', result);
232
236
  return [3 /*break*/, 7];
233
- case 12:
237
+ case 9:
238
+ _g++;
239
+ return [3 /*break*/, 6];
240
+ case 10:
234
241
  // Get rid of deleted and failed jobs
235
- this.jobs = this.jobs.filter(function (j) { return j.status === 'processing'; });
236
- // Check again later, unless we are shutting down and nothing's left
242
+ this.jobs = this.jobs.filter(function (job) {
243
+ if (job.status === 'deleting' || job.status === 'failed') {
244
+ debug('removed', job.message.MessageId);
245
+ delete _this.jobsByMessageId[job.message.MessageId];
246
+ return false;
247
+ }
248
+ else {
249
+ return true;
250
+ }
251
+ });
252
+ // Bail if we are shutting down
237
253
  if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0)
238
254
  return [2 /*return*/];
239
- this.maintainVisibilityTimeout = setTimeout(function () { return _this.maintainVisibility(); }, 10 * 1000);
255
+ msElapsed = new Date() - start;
256
+ msPeriod = this.shutdownRequested ? 1 * 1000 : 10 * 1000;
257
+ msLeft = Math.max(0, msPeriod - msElapsed);
258
+ msMin = this.shutdownRequested ? 1000 : 0;
259
+ nextCheckInMs = Math.max(msMin, msLeft);
260
+ debug({ msElapsed: msElapsed, msPeriod: msPeriod, msLeft: msLeft, msMin: msMin, nextCheckInMs: nextCheckInMs });
261
+ this.maintainVisibilityTimeout = setTimeout(function () {
262
+ _this.maintainPromise = _this.maintainVisibility();
263
+ }, nextCheckInMs);
240
264
  return [2 /*return*/];
241
265
  }
242
266
  });
@@ -244,16 +268,16 @@ var JobExecutor = /** @class */ (function () {
244
268
  };
245
269
  JobExecutor.prototype.executeJob = function (message, callback, qname, qrl) {
246
270
  return __awaiter(this, void 0, void 0, function () {
247
- var payload, visibilityTimeout, job, queue, result, err_1;
271
+ var payload, visibilityTimeout, job, oldJob, e, queue, result, err_1;
248
272
  return __generator(this, function (_a) {
249
273
  switch (_a.label) {
250
274
  case 0:
251
275
  payload = this.opt.json ? JSON.parse(message.Body) : message.Body;
252
- visibilityTimeout = 30;
276
+ visibilityTimeout = 60;
253
277
  job = {
254
278
  status: 'processing',
255
279
  start: new Date(),
256
- visibilityTimeout: 30,
280
+ visibilityTimeout: visibilityTimeout,
257
281
  extendAtSecond: visibilityTimeout / 2,
258
282
  payload: this.opt.json ? JSON.parse(message.Body) : message.Body,
259
283
  message: message,
@@ -261,8 +285,20 @@ var JobExecutor = /** @class */ (function () {
261
285
  qname: qname,
262
286
  qrl: qrl
263
287
  };
264
- debug('executeJob', job);
288
+ oldJob = this.jobsByMessageId[job.message.MessageId];
289
+ if (oldJob) {
290
+ // If we actually see the same job again, we fucked up, probably due to
291
+ // the system being overloaded and us missing our extension call. So
292
+ // we'll celebrate this occasion by throwing a big fat error.
293
+ debug({ oldJob: oldJob });
294
+ e = new Error("Saw job ".concat(oldJob.message.MessageId, " twice"));
295
+ e.job = oldJob;
296
+ // TODO: sentry breadcrumb
297
+ throw e;
298
+ }
299
+ // debug('executeJob', job)
265
300
  this.jobs.push(job);
301
+ this.jobsByMessageId[job.message.MessageId] = job;
266
302
  this.stats.activeJobs++;
267
303
  if (this.opt.verbose) {
268
304
  console.error(chalk_1.default.blue('Executing:'), qname, chalk_1.default.blue('-->'), job.payload);
@@ -271,7 +307,7 @@ var JobExecutor = /** @class */ (function () {
271
307
  console.log(JSON.stringify({
272
308
  event: 'MESSAGE_PROCESSING_START',
273
309
  timestamp: new Date(),
274
- qname: qname,
310
+ qrl: qrl,
275
311
  messageId: message.MessageId,
276
312
  payload: job.payload
277
313
  }));
@@ -304,7 +340,7 @@ var JobExecutor = /** @class */ (function () {
304
340
  return [3 /*break*/, 4];
305
341
  case 3:
306
342
  err_1 = _a.sent();
307
- debug('exec.catch');
343
+ // debug('exec.catch', err)
308
344
  // Fail path for job execution
309
345
  if (this.opt.verbose) {
310
346
  console.error(chalk_1.default.red('FAILED'), message.Body);
@@ -315,7 +351,7 @@ var JobExecutor = /** @class */ (function () {
315
351
  console.log(JSON.stringify({
316
352
  event: 'MESSAGE_PROCESSING_FAILED',
317
353
  reason: 'exception thrown',
318
- qname: qname,
354
+ qrl: qrl,
319
355
  timestamp: new Date(),
320
356
  messageId: message.MessageId,
321
357
  payload: payload,