qdone 2.0.25-alpha → 2.0.27-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.
@@ -118,7 +118,7 @@ exports.getMessages = getMessages;
118
118
  //
119
119
  function processMessages(queues, callback, options) {
120
120
  return __awaiter(this, void 0, void 0, function () {
121
- var opt, lastLatency, systemMonitor, jobExecutor, queueManager, delayTimeout, delay, activeQrls, maxReturnCount, listen, allowedJobs, maxLatency, freeMemory, totalMemory, memoryThreshold, freememThreshold, remainingMemory, latency, latencyFactor, freememFactor, targetJobs, jobsLeft, _i, _a, _b, qname, qrl, maxMessages;
121
+ var opt, lastLatency, systemMonitor, jobExecutor, queueManager, cores, delayTimeout, delay, activeQrls, maxReturnCount, listen, allowedJobs, maxLatency, latency, latencyFactor, freeMemory, totalMemory, memoryThreshold, freememThreshold, remainingMemory, freememFactor, oneMinuteLoad, loadPerCore, loadFactor, overallFactor, targetJobs, jobsLeft, _i, _a, _b, qname, qrl, maxMessages;
122
122
  var _this = this;
123
123
  return __generator(this, function (_c) {
124
124
  switch (_c.label) {
@@ -136,6 +136,7 @@ function processMessages(queues, callback, options) {
136
136
  });
137
137
  jobExecutor = new jobExecutor_js_1.JobExecutor(opt);
138
138
  queueManager = new queueManager_js_1.QueueManager(opt, queues, 60);
139
+ cores = (0, os_1.cpus)().length;
139
140
  delay = function (ms) { return new Promise(function (resolve) {
140
141
  delayTimeout = setTimeout(resolve, ms);
141
142
  }); };
@@ -167,6 +168,9 @@ function processMessages(queues, callback, options) {
167
168
  return __generator(this, function (_a) {
168
169
  switch (_a.label) {
169
170
  case 0:
171
+ if (opt.verbose) {
172
+ console.error(chalk_1.default.blue('Listening on: '), qname);
173
+ }
170
174
  activeQrls.add(qrl);
171
175
  maxReturnCount += maxMessages;
172
176
  _a.label = 1;
@@ -175,16 +179,18 @@ function processMessages(queues, callback, options) {
175
179
  return [4 /*yield*/, getMessages(qrl, opt, maxMessages)];
176
180
  case 2:
177
181
  messages = _a.sent();
178
- if (messages.length) {
179
- for (_i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
180
- message = messages_1[_i];
181
- jobExecutor.executeJob(message, callback, qname, qrl, function () { return queueManager.updateIcehouse(qrl, true); });
182
+ if (!shutdownRequested) {
183
+ if (messages.length) {
184
+ for (_i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
185
+ message = messages_1[_i];
186
+ jobExecutor.executeJob(message, callback, qname, qrl, function () { return queueManager.updateIcehouse(qrl, true); });
187
+ }
188
+ queueManager.updateIcehouse(qrl, false);
189
+ }
190
+ else {
191
+ // If we didn't get any, update the icehouse so we can back off
192
+ queueManager.updateIcehouse(qrl, true);
182
193
  }
183
- queueManager.updateIcehouse(qrl, false);
184
- }
185
- else {
186
- // If we didn't get any, update the icehouse so we can back off
187
- queueManager.updateIcehouse(qrl, true);
188
194
  }
189
195
  // Max job accounting
190
196
  maxReturnCount -= maxMessages;
@@ -209,18 +215,24 @@ function processMessages(queues, callback, options) {
209
215
  if (!!shutdownRequested) return [3 /*break*/, 3];
210
216
  allowedJobs = Math.max(0, opt.maxConcurrentJobs - jobExecutor.activeJobCount() - maxReturnCount);
211
217
  maxLatency = 100;
218
+ latency = systemMonitor.getLatency() || 10;
219
+ latencyFactor = 1 - Math.abs(Math.min(latency / maxLatency, 1)) // 0 if latency is at max, 1 if latency 0
220
+ ;
212
221
  freeMemory = (0, os_1.freemem)();
213
222
  totalMemory = (0, os_1.totalmem)();
214
223
  memoryThreshold = totalMemory * opt.maxMemoryPercent / 100;
215
224
  freememThreshold = totalMemory - memoryThreshold;
216
225
  remainingMemory = Math.max(0, freeMemory - freememThreshold);
217
- latency = systemMonitor.getLatency() || 10;
218
- latencyFactor = 1 - Math.abs(Math.min(latency / maxLatency, 1)) // 0 if latency is at max, 1 if latency 0
219
- ;
220
226
  freememFactor = Math.min(1, Math.max(0, remainingMemory / memoryThreshold));
221
- targetJobs = Math.round(allowedJobs * latencyFactor * freememFactor);
227
+ oneMinuteLoad = (0, os_1.loadavg)()[0];
228
+ loadPerCore = oneMinuteLoad / cores;
229
+ loadFactor = 1 - Math.min(1, Math.max(0, loadPerCore / 3));
230
+ overallFactor = Math.min(latencyFactor, freememFactor, loadFactor);
231
+ targetJobs = Math.round(allowedJobs * overallFactor);
222
232
  jobsLeft = targetJobs;
223
- debug({ jobCount: jobExecutor.activeJobCount(), freeMemory: freeMemory, totalMemory: totalMemory, freememThreshold: freememThreshold, remainingMemory: remainingMemory, memoryThreshold: memoryThreshold, maxReturnCount: maxReturnCount, allowedJobs: allowedJobs, maxLatency: maxLatency, latency: latency, latencyFactor: latencyFactor, freememFactor: freememFactor, targetJobs: targetJobs, activeQrls: activeQrls });
233
+ if (opt.verbose) {
234
+ console.error({ jobCount: jobExecutor.activeJobCount(), freeMemory: freeMemory, totalMemory: totalMemory, freememThreshold: freememThreshold, remainingMemory: remainingMemory, memoryThreshold: memoryThreshold, maxReturnCount: maxReturnCount, allowedJobs: allowedJobs, maxLatency: maxLatency, latency: latency, latencyFactor: latencyFactor, freememFactor: freememFactor, oneMinuteLoad: oneMinuteLoad, loadPerCore: loadPerCore, loadFactor: loadFactor, overallFactor: overallFactor, targetJobs: targetJobs, activeQrls: activeQrls });
235
+ }
224
236
  for (_i = 0, _a = queueManager.getPairs(); _i < _a.length; _i++) {
225
237
  _b = _a[_i], qname = _b.qname, qrl = _b.qrl;
226
238
  // debug({ evaluating: { qname, qrl, jobsLeft, activeQrlsHasQrl: activeQrls.has(qrl) } })
@@ -229,9 +241,6 @@ function processMessages(queues, callback, options) {
229
241
  maxMessages = Math.min(10, jobsLeft);
230
242
  listen(qname, qrl, maxMessages);
231
243
  jobsLeft -= maxMessages;
232
- if (opt.verbose) {
233
- console.error(chalk_1.default.blue('Listening on: '), qname);
234
- }
235
244
  // debug({ listenedTo: { qname, maxMessages, jobsLeft } })
236
245
  }
237
246
  return [4 /*yield*/, delay(1000)];
@@ -74,6 +74,9 @@ var JobExecutor = /** @class */ (function () {
74
74
  this.shutdownRequested = true;
75
75
  // Trigger a maintenance run right away in case it speeds us up
76
76
  clearTimeout(this.maintainVisibilityTimeout);
77
+ if (this.opt.verbose) {
78
+ console.error(chalk_1.default.blue('Shutting down jobExecutor'));
79
+ }
77
80
  return [4 /*yield*/, this.maintainPromise];
78
81
  case 1:
79
82
  _a.sent();
@@ -93,12 +96,24 @@ var JobExecutor = /** @class */ (function () {
93
96
  */
94
97
  JobExecutor.prototype.maintainVisibility = function () {
95
98
  return __awaiter(this, void 0, void 0, function () {
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;
99
+ var nextCheckInMs, 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;
97
100
  var _this = this;
98
101
  return __generator(this, function (_h) {
99
102
  switch (_h.label) {
100
103
  case 0:
104
+ // Bail if we are shutting down
105
+ if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) {
106
+ if (this.opt.verbose) {
107
+ console.error(chalk_1.default.blue('All workers done, finishing shutdown of jobExecutor'));
108
+ }
109
+ return [2 /*return*/];
110
+ }
111
+ // Reset our timeout
101
112
  clearTimeout(this.maintainVisibilityTimeout);
113
+ nextCheckInMs = this.shutdownRequested ? 1000 : 10 * 1000;
114
+ this.maintainVisibilityTimeout = setTimeout(function () {
115
+ _this.maintainPromise = _this.maintainVisibility();
116
+ }, nextCheckInMs);
102
117
  start = new Date();
103
118
  jobsToExtendByQrl = {};
104
119
  jobsToDeleteByQrl = {};
@@ -249,18 +264,6 @@ var JobExecutor = /** @class */ (function () {
249
264
  return true;
250
265
  }
251
266
  });
252
- // Bail if we are shutting down
253
- if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0)
254
- return [2 /*return*/];
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);
264
267
  return [2 /*return*/];
265
268
  }
266
269
  });
@@ -272,6 +275,8 @@ var JobExecutor = /** @class */ (function () {
272
275
  return __generator(this, function (_a) {
273
276
  switch (_a.label) {
274
277
  case 0:
278
+ if (this.shutdownRequested)
279
+ throw new Error('jobExecutor is shutting down so cannot execute new job');
275
280
  payload = this.opt.json ? JSON.parse(message.Body) : message.Body;
276
281
  visibilityTimeout = 60;
277
282
  job = {
@@ -114,6 +114,12 @@ var QueueManager = /** @class */ (function () {
114
114
  clearTimeout(this.resolveTimeout);
115
115
  if (this.shutdownRequested)
116
116
  return [2 /*return*/];
117
+ this.resolveTimeout = setTimeout(function () {
118
+ _this.resolvePromise = _this.resolveQueues();
119
+ }, this.resolveSeconds * 1000);
120
+ if (this.opt.verbose) {
121
+ console.error(chalk_1.default.blue('Will resolve queues again in ' + this.resolveSeconds + ' seconds'));
122
+ }
117
123
  qnames = this.queues.map(function (queue) { return (0, qrlCache_js_1.normalizeQueueName)(queue, _this.opt); });
118
124
  return [4 /*yield*/, (0, qrlCache_js_1.getQnameUrlPairs)(qnames, this.opt)];
119
125
  case 1:
@@ -183,15 +189,9 @@ var QueueManager = /** @class */ (function () {
183
189
  debug('selectedPairs', this.selectedPairs);
184
190
  // Finished resolving
185
191
  if (this.opt.verbose) {
186
- console.error(chalk_1.default.blue(' done'));
192
+ console.error(chalk_1.default.blue('Done resolving'));
187
193
  console.error();
188
194
  }
189
- if (this.opt.verbose) {
190
- console.error(chalk_1.default.blue('Will resolve queues again in ' + this.resolveSeconds + ' seconds'));
191
- }
192
- this.resolveTimeout = setTimeout(function () {
193
- _this.resolvePromise = _this.resolveQueues();
194
- }, this.resolveSeconds * 1000);
195
195
  return [2 /*return*/];
196
196
  }
197
197
  });
@@ -218,6 +218,9 @@ var QueueManager = /** @class */ (function () {
218
218
  case 0:
219
219
  this.shutdownRequested = true;
220
220
  clearTimeout(this.resolveTimeout);
221
+ if (this.opt.verbose) {
222
+ console.error(chalk_1.default.blue('Waiting for queues to resolve'));
223
+ }
221
224
  return [4 /*yield*/, this.resolvePromise];
222
225
  case 1:
223
226
  _a.sent();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdone",
3
- "version": "2.0.25-alpha",
3
+ "version": "2.0.27-alpha",
4
4
  "description": "Language agnostic job queue for SQS",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/src/consumer.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Consumer implementation.
3
3
  */
4
4
 
5
- import { freemem, totalmem } from 'os'
5
+ import { freemem, totalmem, loadavg, cpus } from 'os'
6
6
  import { ReceiveMessageCommand, QueueDoesNotExist } from '@aws-sdk/client-sqs'
7
7
  import chalk from 'chalk'
8
8
  import Debug from 'debug'
@@ -62,6 +62,7 @@ export async function processMessages (queues, callback, options) {
62
62
  })
63
63
  const jobExecutor = new JobExecutor(opt)
64
64
  const queueManager = new QueueManager(opt, queues, 60)
65
+ const cores = cpus().length
65
66
  // debug({ systemMonitor, jobExecutor, queueManager })
66
67
 
67
68
  // This delay function keeps a timeout reference around so it can be
@@ -85,18 +86,24 @@ export async function processMessages (queues, callback, options) {
85
86
  const activeQrls = new Set()
86
87
  let maxReturnCount = 0
87
88
  const listen = async (qname, qrl, maxMessages) => {
89
+ if (opt.verbose) {
90
+ console.error(chalk.blue('Listening on: '), qname)
91
+ }
88
92
  activeQrls.add(qrl)
89
93
  maxReturnCount += maxMessages
90
94
  try {
91
95
  const messages = await getMessages(qrl, opt, maxMessages)
92
- if (messages.length) {
93
- for (const message of messages) {
94
- jobExecutor.executeJob(message, callback, qname, qrl, () => queueManager.updateIcehouse(qrl, true))
96
+
97
+ if (!shutdownRequested) {
98
+ if (messages.length) {
99
+ for (const message of messages) {
100
+ jobExecutor.executeJob(message, callback, qname, qrl, () => queueManager.updateIcehouse(qrl, true))
101
+ }
102
+ queueManager.updateIcehouse(qrl, false)
103
+ } else {
104
+ // If we didn't get any, update the icehouse so we can back off
105
+ queueManager.updateIcehouse(qrl, true)
95
106
  }
96
- queueManager.updateIcehouse(qrl, false)
97
- } else {
98
- // If we didn't get any, update the icehouse so we can back off
99
- queueManager.updateIcehouse(qrl, true)
100
107
  }
101
108
 
102
109
  // Max job accounting
@@ -115,27 +122,38 @@ export async function processMessages (queues, callback, options) {
115
122
  while (!shutdownRequested) { // eslint-disable-line
116
123
  // Figure out how we are running
117
124
  const allowedJobs = Math.max(0, opt.maxConcurrentJobs - jobExecutor.activeJobCount() - maxReturnCount)
125
+
126
+ // Latency
118
127
  const maxLatency = 100
128
+ const latency = systemMonitor.getLatency() || 10
129
+ const latencyFactor = 1 - Math.abs(Math.min(latency / maxLatency, 1)) // 0 if latency is at max, 1 if latency 0
130
+
131
+ // Memory
119
132
  const freeMemory = freemem()
120
133
  const totalMemory = totalmem()
121
134
  const memoryThreshold = totalMemory * opt.maxMemoryPercent / 100
122
135
  const freememThreshold = totalMemory - memoryThreshold
123
136
  const remainingMemory = Math.max(0, freeMemory - freememThreshold)
124
- const latency = systemMonitor.getLatency() || 10
125
- const latencyFactor = 1 - Math.abs(Math.min(latency / maxLatency, 1)) // 0 if latency is at max, 1 if latency 0
126
137
  const freememFactor = Math.min(1, Math.max(0, remainingMemory / memoryThreshold))
127
- const targetJobs = Math.round(allowedJobs * latencyFactor * freememFactor)
138
+
139
+ // Load
140
+ const oneMinuteLoad = loadavg()[0]
141
+ const loadPerCore = oneMinuteLoad / cores
142
+ const loadFactor = 1 - Math.min(1, Math.max(0, loadPerCore / 3))
143
+
144
+ const overallFactor = Math.min(latencyFactor, freememFactor, loadFactor)
145
+ const targetJobs = Math.round(allowedJobs * overallFactor)
128
146
  let jobsLeft = targetJobs
129
- debug({ jobCount: jobExecutor.activeJobCount(), freeMemory, totalMemory, freememThreshold, remainingMemory, memoryThreshold, maxReturnCount, allowedJobs, maxLatency, latency, latencyFactor, freememFactor, targetJobs, activeQrls })
147
+
148
+ if (opt.verbose) {
149
+ console.error({ jobCount: jobExecutor.activeJobCount(), freeMemory, totalMemory, freememThreshold, remainingMemory, memoryThreshold, maxReturnCount, allowedJobs, maxLatency, latency, latencyFactor, freememFactor, oneMinuteLoad, loadPerCore, loadFactor, overallFactor, targetJobs, activeQrls })
150
+ }
130
151
  for (const { qname, qrl } of queueManager.getPairs()) {
131
152
  // debug({ evaluating: { qname, qrl, jobsLeft, activeQrlsHasQrl: activeQrls.has(qrl) } })
132
153
  if (jobsLeft <= 0 || activeQrls.has(qrl)) continue
133
154
  const maxMessages = Math.min(10, jobsLeft)
134
155
  listen(qname, qrl, maxMessages)
135
156
  jobsLeft -= maxMessages
136
- if (opt.verbose) {
137
- console.error(chalk.blue('Listening on: '), qname)
138
- }
139
157
  // debug({ listenedTo: { qname, maxMessages, jobsLeft } })
140
158
  }
141
159
  await delay(1000)
@@ -35,6 +35,9 @@ export class JobExecutor {
35
35
  this.shutdownRequested = true
36
36
  // Trigger a maintenance run right away in case it speeds us up
37
37
  clearTimeout(this.maintainVisibilityTimeout)
38
+ if (this.opt.verbose) {
39
+ console.error(chalk.blue('Shutting down jobExecutor'))
40
+ }
38
41
  await this.maintainPromise
39
42
  await this.maintainVisibility()
40
43
  }
@@ -47,7 +50,21 @@ export class JobExecutor {
47
50
  * Changes message visibility on all running jobs using as few calls as possible.
48
51
  */
49
52
  async maintainVisibility () {
53
+ // Bail if we are shutting down
54
+ if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) {
55
+ if (this.opt.verbose) {
56
+ console.error(chalk.blue('All workers done, finishing shutdown of jobExecutor'))
57
+ }
58
+ return
59
+ }
60
+
61
+ // Reset our timeout
50
62
  clearTimeout(this.maintainVisibilityTimeout)
63
+ const nextCheckInMs = this.shutdownRequested ? 1000 : 10 * 1000
64
+ this.maintainVisibilityTimeout = setTimeout(() => {
65
+ this.maintainPromise = this.maintainVisibility()
66
+ }, nextCheckInMs)
67
+
51
68
  // debug('maintainVisibility', this.jobs)
52
69
  const start = new Date()
53
70
  const jobsToExtendByQrl = {}
@@ -176,24 +193,10 @@ export class JobExecutor {
176
193
  return true
177
194
  }
178
195
  })
179
-
180
- // Bail if we are shutting down
181
- if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) return
182
-
183
- // Check later, but count the time we spent. Make sure we check at least
184
- // every period seconds.
185
- const msElapsed = new Date() - start
186
- const msPeriod = this.shutdownRequested ? 1 * 1000 : 10 * 1000
187
- const msLeft = Math.max(0, msPeriod - msElapsed)
188
- const msMin = this.shutdownRequested ? 1000 : 0
189
- const nextCheckInMs = Math.max(msMin, msLeft)
190
- debug({ msElapsed, msPeriod, msLeft, msMin, nextCheckInMs })
191
- this.maintainVisibilityTimeout = setTimeout(() => {
192
- this.maintainPromise = this.maintainVisibility()
193
- }, nextCheckInMs)
194
196
  }
195
197
 
196
198
  async executeJob (message, callback, qname, qrl, failedCallback) {
199
+ if (this.shutdownRequested) throw new Error('jobExecutor is shutting down so cannot execute new job')
197
200
  // Create job entry and track it
198
201
  const payload = this.opt.json ? JSON.parse(message.Body) : message.Body
199
202
  const visibilityTimeout = 60
@@ -69,6 +69,12 @@ export class QueueManager {
69
69
  async resolveQueues () {
70
70
  clearTimeout(this.resolveTimeout)
71
71
  if (this.shutdownRequested) return
72
+ this.resolveTimeout = setTimeout(() => {
73
+ this.resolvePromise = this.resolveQueues()
74
+ }, this.resolveSeconds * 1000)
75
+ if (this.opt.verbose) {
76
+ console.error(chalk.blue('Will resolve queues again in ' + this.resolveSeconds + ' seconds'))
77
+ }
72
78
 
73
79
  // Start processing
74
80
  const qnames = this.queues.map(queue => normalizeQueueName(queue, this.opt))
@@ -120,16 +126,9 @@ export class QueueManager {
120
126
 
121
127
  // Finished resolving
122
128
  if (this.opt.verbose) {
123
- console.error(chalk.blue(' done'))
129
+ console.error(chalk.blue('Done resolving'))
124
130
  console.error()
125
131
  }
126
-
127
- if (this.opt.verbose) {
128
- console.error(chalk.blue('Will resolve queues again in ' + this.resolveSeconds + ' seconds'))
129
- }
130
- this.resolveTimeout = setTimeout(() => {
131
- this.resolvePromise = this.resolveQueues()
132
- }, this.resolveSeconds * 1000)
133
132
  }
134
133
 
135
134
  // Return the next queue in the lineup
@@ -147,6 +146,9 @@ export class QueueManager {
147
146
  async shutdown () {
148
147
  this.shutdownRequested = true
149
148
  clearTimeout(this.resolveTimeout)
149
+ if (this.opt.verbose) {
150
+ console.error(chalk.blue('Waiting for queues to resolve'))
151
+ }
150
152
  await this.resolvePromise
151
153
  }
152
154
  }